pm3-rs-python 0.1.2__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 (183) hide show
  1. pm3_rs_python-0.2.5/CHANGELOG.md +971 -0
  2. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/Cargo.lock +1 -1
  3. pm3_rs_python-0.2.5/Cargo.toml +44 -0
  4. pm3_rs_python-0.2.5/PKG-INFO +255 -0
  5. pm3_rs_python-0.2.5/README.md +212 -0
  6. pm3_rs_python-0.2.5/THIRD_PARTY_NOTICES.md +115 -0
  7. pm3_rs_python-0.2.5/docs/divide-and-conquer.md +268 -0
  8. pm3_rs_python-0.2.5/docs/pbc.md +737 -0
  9. pm3_rs_python-0.2.5/docs/python-api.md +646 -0
  10. pm3_rs_python-0.2.5/docs/rust-api.md +389 -0
  11. pm3_rs_python-0.2.5/docs/scope.md +245 -0
  12. pm3_rs_python-0.2.5/pyproject.toml +115 -0
  13. pm3_rs_python-0.2.5/python/pm3_rs/__init__.py +80 -0
  14. pm3_rs_python-0.2.5/python/pm3_rs/_native.pyi +306 -0
  15. pm3_rs_python-0.2.5/python/pm3_rs/ase.py +1062 -0
  16. pm3_rs_python-0.2.5/python/pm3_rs/cli.py +31 -0
  17. pm3_rs_python-0.2.5/python/pm3_rs/native.py +1283 -0
  18. pm3_rs_python-0.2.5/python/pm3_rs/py.typed +0 -0
  19. pm3_rs_python-0.2.5/src/bin/pm3_rs.rs +12 -0
  20. pm3_rs_python-0.2.5/src/cell.rs +616 -0
  21. pm3_rs_python-0.2.5/src/cli.rs +2398 -0
  22. pm3_rs_python-0.2.5/src/cmatrix.rs +342 -0
  23. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/constants.rs +43 -0
  24. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/corrections/d3.rs +82 -7
  25. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/corrections/h4.rs +29 -7
  26. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/corrections/hx.rs +12 -1
  27. pm3_rs_python-0.2.5/src/corrections/mmok.rs +224 -0
  28. pm3_rs_python-0.2.5/src/corrections/mod.rs +250 -0
  29. pm3_rs_python-0.2.5/src/corrections/periodic.rs +477 -0
  30. pm3_rs_python-0.2.5/src/dc/derivatives.rs +316 -0
  31. pm3_rs_python-0.2.5/src/dc/mod.rs +53 -0
  32. pm3_rs_python-0.2.5/src/dc/partition.rs +407 -0
  33. pm3_rs_python-0.2.5/src/dc/pattern.rs +423 -0
  34. pm3_rs_python-0.2.5/src/dc/scf.rs +1159 -0
  35. pm3_rs_python-0.2.5/src/densitydiis.rs +213 -0
  36. pm3_rs_python-0.2.5/src/dipole.rs +354 -0
  37. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/error.rs +22 -4
  38. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/fock.rs +144 -8
  39. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/gradient.rs +108 -6
  40. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/hamiltonian.rs +60 -2
  41. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/hessian.rs +697 -86
  42. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/integrals_d.rs +5 -5
  43. pm3_rs_python-0.2.5/src/ir.rs +562 -0
  44. pm3_rs_python-0.2.5/src/lib.rs +109 -0
  45. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/linalg.rs +1 -6
  46. pm3_rs_python-0.2.5/src/molden.rs +826 -0
  47. pm3_rs_python-0.2.5/src/neighbor.rs +467 -0
  48. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/optimizer.rs +147 -19
  49. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/overlap.rs +179 -30
  50. pm3_rs_python-0.2.5/src/pbc/berry.rs +401 -0
  51. pm3_rs_python-0.2.5/src/pbc/born.rs +190 -0
  52. pm3_rs_python-0.2.5/src/pbc/dfpt.rs +5119 -0
  53. pm3_rs_python-0.2.5/src/pbc/dielectric.rs +385 -0
  54. pm3_rs_python-0.2.5/src/pbc/ewald.rs +3412 -0
  55. pm3_rs_python-0.2.5/src/pbc/ewald_hessian.rs +675 -0
  56. pm3_rs_python-0.2.5/src/pbc/ewald_reference.rs +406 -0
  57. pm3_rs_python-0.2.5/src/pbc/finite_field.rs +649 -0
  58. pm3_rs_python-0.2.5/src/pbc/gamma.rs +2346 -0
  59. pm3_rs_python-0.2.5/src/pbc/gradient.rs +989 -0
  60. pm3_rs_python-0.2.5/src/pbc/hessian.rs +1153 -0
  61. pm3_rs_python-0.2.5/src/pbc/kernel.rs +520 -0
  62. pm3_rs_python-0.2.5/src/pbc/kpoints.rs +662 -0
  63. pm3_rs_python-0.2.5/src/pbc/kscf.rs +2507 -0
  64. pm3_rs_python-0.2.5/src/pbc/lo_to.rs +163 -0
  65. pm3_rs_python-0.2.5/src/pbc/mod.rs +98 -0
  66. pm3_rs_python-0.2.5/src/pbc/multipole.rs +864 -0
  67. pm3_rs_python-0.2.5/src/pbc/optimize.rs +567 -0
  68. pm3_rs_python-0.2.5/src/pbc/phased.rs +1709 -0
  69. pm3_rs_python-0.2.5/src/pbc/phonon.rs +381 -0
  70. pm3_rs_python-0.2.5/src/pbc/screen.rs +628 -0
  71. pm3_rs_python-0.2.5/src/python.rs +2253 -0
  72. pm3_rs_python-0.2.5/src/rigid.rs +543 -0
  73. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/scf.rs +646 -63
  74. pm3_rs_python-0.2.5/src/special.rs +280 -0
  75. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/system.rs +71 -6
  76. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/tests/api_surface.rs +172 -19
  77. pm3_rs_python-0.2.5/tests/attribution.rs +426 -0
  78. pm3_rs_python-0.2.5/tests/crystals.rs +438 -0
  79. pm3_rs_python-0.2.5/tests/data/mopac_oracle.tsv +204 -0
  80. pm3_rs_python-0.2.5/tests/molecules.rs +544 -0
  81. pm3_rs_python-0.2.5/tests/mopac_oracle.rs +443 -0
  82. pm3_rs_python-0.2.5/tests/pbc_berry.rs +313 -0
  83. pm3_rs_python-0.2.5/tests/pbc_born_charges.rs +256 -0
  84. pm3_rs_python-0.2.5/tests/pbc_cubic_identities.rs +280 -0
  85. pm3_rs_python-0.2.5/tests/pbc_dfpt_options.rs +272 -0
  86. pm3_rs_python-0.2.5/tests/pbc_dielectric.rs +367 -0
  87. pm3_rs_python-0.2.5/tests/pbc_finite_field.rs +366 -0
  88. pm3_rs_python-0.2.5/tests/pbc_lo_to.rs +243 -0
  89. pm3_rs_python-0.2.5/tests/pbc_phonon.rs +229 -0
  90. pm3_rs_python-0.2.5/tests/pbc_scf_diagnosis.rs +379 -0
  91. pm3_rs_python-0.2.5/tests/pbc_uhf_response.rs +258 -0
  92. pm3_rs_python-0.2.5/tests/test_api_key_contract.py +478 -0
  93. pm3_rs_python-0.2.5/tests/test_ase_npt.py +351 -0
  94. pm3_rs_python-0.2.5/tests/test_periodic_python_api.py +788 -0
  95. pm3_rs_python-0.2.5/tests/test_python_api.py +1159 -0
  96. pm3_rs_python-0.2.5/third_party/README.md +60 -0
  97. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/third_party/mopac/LICENSE +176 -176
  98. pm3_rs_python-0.2.5/third_party/mopac/NOTICE +78 -0
  99. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/third_party/pyseqm/NOTICE +12 -3
  100. pm3_rs_python-0.2.5/third_party/rust-crates/LICENSES.txt +10114 -0
  101. pm3_rs_python-0.2.5/third_party/rust-crates/NOTICE +119 -0
  102. pm3_rs_python-0.1.2/CHANGELOG.md +0 -101
  103. pm3_rs_python-0.1.2/Cargo.toml +0 -33
  104. pm3_rs_python-0.1.2/PKG-INFO +0 -160
  105. pm3_rs_python-0.1.2/README.md +0 -146
  106. pm3_rs_python-0.1.2/THIRD_PARTY_NOTICES.md +0 -63
  107. pm3_rs_python-0.1.2/docs/python-api.md +0 -172
  108. pm3_rs_python-0.1.2/docs/rust-api.md +0 -168
  109. pm3_rs_python-0.1.2/docs/scope.md +0 -46
  110. pm3_rs_python-0.1.2/examples/ammonia.xyz +0 -6
  111. pm3_rs_python-0.1.2/examples/bench102.xyz +0 -104
  112. pm3_rs_python-0.1.2/examples/ethanol.xyz +0 -11
  113. pm3_rs_python-0.1.2/examples/formaldehyde.xyz +0 -6
  114. pm3_rs_python-0.1.2/examples/h2s.xyz +0 -5
  115. pm3_rs_python-0.1.2/examples/hcl.xyz +0 -4
  116. pm3_rs_python-0.1.2/examples/methane.xyz +0 -7
  117. pm3_rs_python-0.1.2/examples/methyl_radical.xyz +0 -6
  118. pm3_rs_python-0.1.2/examples/ticl4.xyz +0 -7
  119. pm3_rs_python-0.1.2/examples/water.xyz +0 -5
  120. pm3_rs_python-0.1.2/examples/water_distorted.xyz +0 -5
  121. pm3_rs_python-0.1.2/pyproject.toml +0 -21
  122. pm3_rs_python-0.1.2/python/pm3_rs/__init__.py +0 -27
  123. pm3_rs_python-0.1.2/python/pm3_rs/ase.py +0 -175
  124. pm3_rs_python-0.1.2/python/pm3_rs/native.py +0 -175
  125. pm3_rs_python-0.1.2/src/bin/pm3_rs.rs +0 -255
  126. pm3_rs_python-0.1.2/src/corrections/mod.rs +0 -138
  127. pm3_rs_python-0.1.2/src/lib.rs +0 -60
  128. pm3_rs_python-0.1.2/src/python.rs +0 -376
  129. pm3_rs_python-0.1.2/tests/molecules.rs +0 -250
  130. pm3_rs_python-0.1.2/tests/test_python_api.py +0 -370
  131. pm3_rs_python-0.1.2/tools/extract_d3_data.py +0 -131
  132. pm3_rs_python-0.1.2/tools/extract_pm3_params.py +0 -196
  133. pm3_rs_python-0.1.2/tools/extract_report.md +0 -9
  134. pm3_rs_python-0.1.2/tools/oracle/ALL_ELEMENTS_FORCE_RESULTS.json +0 -1035
  135. pm3_rs_python-0.1.2/tools/oracle/ALL_ELEMENTS_RESULTS.json +0 -1035
  136. pm3_rs_python-0.1.2/tools/oracle/HEAVY_HALOGEN_FD005_RESULTS.json +0 -66
  137. pm3_rs_python-0.1.2/tools/oracle/PM3_VALIDATION.md +0 -114
  138. pm3_rs_python-0.1.2/tools/oracle/all_element_validation.py +0 -413
  139. pm3_rs_python-0.1.2/tools/oracle/molecules/ammonia.xyz +0 -6
  140. pm3_rs_python-0.1.2/tools/oracle/molecules/ar2.xyz +0 -4
  141. pm3_rs_python-0.1.2/tools/oracle/molecules/capped_methyl.xyz +0 -7
  142. pm3_rs_python-0.1.2/tools/oracle/molecules/formaldehyde.xyz +0 -6
  143. pm3_rs_python-0.1.2/tools/oracle/molecules/gdf3.xyz +0 -6
  144. pm3_rs_python-0.1.2/tools/oracle/molecules/h2s.xyz +0 -5
  145. pm3_rs_python-0.1.2/tools/oracle/molecules/hcl.xyz +0 -4
  146. pm3_rs_python-0.1.2/tools/oracle/molecules/heh_plus.xyz +0 -4
  147. pm3_rs_python-0.1.2/tools/oracle/molecules/methane.xyz +0 -7
  148. pm3_rs_python-0.1.2/tools/oracle/molecules/methyl_radical.xyz +0 -6
  149. pm3_rs_python-0.1.2/tools/oracle/molecules/ph3.xyz +0 -6
  150. pm3_rs_python-0.1.2/tools/oracle/molecules/sf6.xyz +0 -9
  151. pm3_rs_python-0.1.2/tools/oracle/molecules/sih4.xyz +0 -7
  152. pm3_rs_python-0.1.2/tools/oracle/molecules/ticl4.xyz +0 -7
  153. pm3_rs_python-0.1.2/tools/oracle/molecules/water_dimer.xyz +0 -8
  154. pm3_rs_python-0.1.2/tools/oracle/molecules/water_minus.xyz +0 -6
  155. pm3_rs_python-0.1.2/tools/oracle/molecules/water_plus.xyz +0 -6
  156. pm3_rs_python-0.1.2/tools/oracle/pair_sweep.py +0 -216
  157. pm3_rs_python-0.1.2/tools/oracle/run_mopac.py +0 -244
  158. pm3_rs_python-0.1.2/tools/oracle/sweep.py +0 -134
  159. pm3_rs_python-0.1.2/tools/verify_pair_params.py +0 -86
  160. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/LICENSE +0 -0
  161. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/basis.rs +0 -0
  162. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data/d3_c6_reference.csv +0 -0
  163. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data/d3_r0ab.csv +0 -0
  164. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data/d3_radii.csv +0 -0
  165. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data/element_data.csv +0 -0
  166. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data/pm3_global.csv +0 -0
  167. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data/pm3_pair_parameters.csv +0 -0
  168. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data/pm3_parameters.csv +0 -0
  169. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data/pm3_sparkles.csv +0 -0
  170. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/data_tables.rs +0 -0
  171. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/dual.rs +0 -0
  172. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/dual2.rs +0 -0
  173. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/frame.rs +0 -0
  174. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/integrals.rs +0 -0
  175. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/math.rs +0 -0
  176. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/onecenter.rs +0 -0
  177. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/overlap_numeric.rs +0 -0
  178. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/params.rs +0 -0
  179. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/repulsion.rs +0 -0
  180. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/src/rotations.rs +0 -0
  181. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/tests/test_python.py +0 -0
  182. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/third_party/dftd3/NOTICE +0 -0
  183. {pm3_rs_python-0.1.2 → pm3_rs_python-0.2.5}/third_party/h_bonds4/NOTICE +0 -0
@@ -0,0 +1,971 @@
1
+ # Changelog
2
+
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
283
+
284
+ One performance fix, found by running real crystals rather than by reading code,
285
+ and two reporting defects it turned up on the way.
286
+
287
+ ### Faster
288
+
289
+ - **Born charges and Γ-point phonons are ~2.4× faster**, and the numbers are
290
+ unchanged. The long-range term of the bare perturbation ran two lattice sums
291
+ over the site pairs *inside* the per-degree-of-freedom loop — `6N` sums where
292
+ two suffice. The displacements do not depend on the Cartesian axis at all, so
293
+ each was recomputed three times per atom for nothing, and across atoms the
294
+ sets are disjoint slices of one site-by-site table. It is now built once
295
+ (`pbc::dfpt::LongRangeKernels`).
296
+
297
+ At 12 atoms a Born-charge run went from 62.2 s to 26.3 s; the Rust suite's
298
+ periodic tests roughly halved.
299
+
300
+ Worth recording how it was found, because three earlier attempts missed it.
301
+ Replacing the `3N` coupled-perturbed solves with three by the interchange
302
+ theorem changed the wall clock by **nothing**; hoisting the neighbour list out
303
+ of the bare perturbations changed **nothing**; parallelising the three field
304
+ solves changed **nothing**. Timing the pieces put 85% of the run in one
305
+ untimed call. The lesson is in `examples/born_profile.rs`, which decomposes
306
+ the cost with public entry points that differ in one term each.
307
+
308
+ ### Fixed
309
+
310
+ - **`Pm3Error::ScfNotConverged` always reported `error: NaN`.** It was
311
+ hardcoded at all four sites — the Γ path, the k-point path and both molecular
312
+ paths — so the field meant to say how far off a run was never did, and `NaN`
313
+ reads as a numerical blow-up when the truth was usually a slow tail. Diamond's
314
+ conventional cell reports `3.854e-4` against a `1e-7` tolerance; it was not
315
+ diverging at all.
316
+ - **A failed Γ-point SCF never mentioned the Γ margin**, which is usually the
317
+ cause. Below it one k-point cannot represent the cell and no amount of damping
318
+ helps. The message now gives the margin and says to use a k-mesh or a larger
319
+ supercell. Measured on NaCl's conventional cell, where the Γ answer and the
320
+ 4×4×4 one differ by **421 eV**.
321
+
322
+ ### Housekeeping
323
+
324
+ - `cargo fmt` across the tree: 0.2.2 shipped 26 files that rustfmt disagreed
325
+ with, all of them new or edited in that release.
326
+
327
+ ## 0.2.2
328
+
329
+ The response properties a dynamical matrix was already most of the way to, and a
330
+ Berry phase to check them from outside. The largest fix is none of those: a
331
+ coupled-perturbed solver that had been returning unconverged responses under a
332
+ converged label, which every analytic Hessian and infrared intensity this crate
333
+ has produced was built on.
334
+
335
+ ### Added
336
+
337
+ - **Born effective charges** (`pbc::born`, `pm3_rs.born_charges`, CLI `born`,
338
+ `PM3.get_born_charges`), reported with the acoustic sum-rule residual rather
339
+ than with the rule imposed.
340
+ - **Polarizability and the dielectric tensors** (`pbc::dielectric`,
341
+ `pm3_rs.dielectric`, CLI `dielectric`, `PM3.get_dielectric`): `ε∞` for a
342
+ fully periodic cell, `α` in every dimensionality, and — with `include_ionic`
343
+ / `--static` — the static tensor `ε₀`. `skipped_modes` says how much of the
344
+ ionic sum is missing, since at an unrelaxed geometry the answer is incomplete
345
+ rather than merely odd.
346
+ - **LO–TO splitting** (`pbc::lo_to`, `phonons(lo_to_direction=)`,
347
+ `--lo-to`). The coefficient was not transcribed: it was fixed against this
348
+ crate's own finite-`q` DFPT in the `q → 0` limit, where the ratio converges to
349
+ 1 as `q²` (0.56 → 0.89 → 0.973).
350
+ - **Supercell force constants and phonon dispersion** (`pbc::phonon`,
351
+ `ForceConstants`, `pm3_rs.phonon_bands`, CLI `phonon-bands`,
352
+ `PM3.get_phonon_bands`). One Hessian buys the whole band structure. `Φ(T)`
353
+ lives in a sorted vector, not a hash map: the Fourier sum's accumulation order
354
+ is otherwise run-dependent, which in a near-degenerate mode is not a
355
+ last-digit effect.
356
+ - **Berry-phase polarization** (`pbc::berry`, `pm3_rs.berry_polarization`, CLI
357
+ `berry`, `PM3.get_berry_polarization`), as an independent check on the above
358
+ rather than as a feature in itself.
359
+ - **A finite electric field along a periodic direction** (`pbc::finite_field`,
360
+ `pm3_rs.finite_field`, CLI `finite-field`, `PM3.get_finite_field`), by the
361
+ Nunes–Gonze electric enthalpy `F = E − Ω 𝓔·P`. `𝓔·R` is not lattice-periodic
362
+ along a periodic axis, so `H − 𝓔·R` has no ground state there; a field
363
+ orthogonal to every lattice vector still goes through `Pm3Options::field`.
364
+ Reproduces the CPHF polarizability to 1 part in 10⁴. Restricted, gapped, 3D
365
+ only, and there is no force.
366
+ - **`DfptOptions` / `LongRange` / `DfptResult`**, `force_constants_at_q`,
367
+ `frequencies_at_q`, and the response density (`PhononResponse`) everything
368
+ above contracts.
369
+ - **The Mermin electronic free energy**: `KpointResult::entropy_ts_ev` and
370
+ `free_energy_ev`, and ASE's `free_energy`, so
371
+ `get_potential_energy(force_consistent=True)` works. Documented in four places
372
+ as *not* a Gibbs energy — no zero-point energy, no vibrational partition
373
+ function, no `pV`, no nuclear entropy.
374
+ - **`divide_and_conquer_forces`**, `periodic_hessian`, `magnetization`,
375
+ `smearing_ev`, CLI `--reference`, and the mode vectors and masses
376
+ `frequencies` and `dynamical_matrix` had been withholding.
377
+
378
+ ### Fixed
379
+
380
+ - **CPHF returned unconverged responses as converged.** All four paths ran to an
381
+ iteration limit and returned `Ok`. Adding the check failed eight existing
382
+ tests, which exposed the cause: `hessian.rs`'s own DIIS divided `B_ij` by each
383
+ residual's magnitude, replacing the constraint `Σc = 1` with a different one.
384
+ The correct implementation was already in `scf.rs`. Water's response went from
385
+ 160 passes to **3** (residual `8.1e-17`); the Rust suite from 38.7 s to 31.4 s.
386
+ - **`D(q)` diverged as `1/q²`** in a polar cell — the acoustic sum rule read
387
+ 220.1 at `q = 0.0125`, and water in a 10 Bohr cell put its lowest mode at
388
+ −4705 cm⁻¹. The `G = 0` term of the bare long-range perturbation was scaled by
389
+ the displaced atom's own charge rather than the cell's, so `Σ_a Q_a = 0` never
390
+ cancelled it. Fixed by excluding the macroscopic field from the
391
+ self-consistent response and restoring it analytically; the term restored is
392
+ exactly `pbc::lo_to`. No existing DFPT test ran below `q = 0.2`.
393
+ - **`dynamical_matrix_on_mesh` built its skeleton from the Γ density**, so
394
+ passing a mesh corrected the response and left the dominant term wrong.
395
+ - **ASE ran two SCF calculations per step and stitched them together**, taking
396
+ energy and forces from an unsmeared one and charges from a smeared one — on a
397
+ metal, different states. Now one call; the Python suite went 99 s → 51 s.
398
+ - **UHF CPHF had no DIIS**, so the hardest case was the one without acceleration.
399
+ - **Open-shell HOMO/LUMO ignored the β spectrum.**
400
+ - **`stress_tol` crossed the Python boundary without its unit conversion**,
401
+ leaving the periodic optimizer 6.75× looser than documented and returning
402
+ relaxed cells with `converged=True`.
403
+ - **`long_range_cutoff` was silently ignored by periodic divide-and-conquer**,
404
+ and `--kpts` by three CLI paths that cannot sample a mesh. Both now refuse.
405
+ - **ASE's result cache was not invalidated by attribute changes**, so setting
406
+ `atoms.calc.charge = 1` returned the neutral energy.
407
+ - **`vibrational_analysis` divided by zero mass** for MOPAC's sparkles.
408
+
409
+ ### Fixed in the new code, by cross-checks rather than by review
410
+
411
+ - **The field operator was half its correct size.** `M = i λ (W₊ − W₋) C†` is
412
+ one-sided — `M|v⟩ = 0` for a virtual `v` — so it holds the whole
413
+ virtual-occupied block and none of the occupied-virtual one. The conventional
414
+ `½(M + M†)` therefore halves the block a linear response is made of. Measured
415
+ at a ratio of `0.5001` against the CPHF polarizability; `M + M†` gives
416
+ `1.0003`.
417
+ - **The finite field computed polarization only along the axes the field
418
+ touched.** Those are not the axes that carry polarization: a zero field came
419
+ back with an electronic polarization of exactly zero. `resolved` now reports
420
+ which axes the mesh could see.
421
+ - **The CLI handed `run_finite_field` a negated field.** `--field` is stored
422
+ with the sign that makes `E = E₀ + μ·F` hold for the molecular `−𝓔·r`
423
+ coupling. Nothing failed — the loop converged and `∂P/∂𝓔` was backwards.
424
+
425
+ ### Wiring
426
+
427
+ A layer-by-layer audit found four features present in some layers and not
428
+ others, the same shape of gap as the "DFPT is Rust-only" report:
429
+ `berry_polarization` was Rust-only, supercell dispersion was CLI-only, the
430
+ finite field reached neither ASE nor the CLI, and LO–TO was unreachable from the
431
+ CLI. All four are now in every layer.
432
+
433
+ ### Measured, and not fixed
434
+
435
+ - **The correction Hessian is still `O(N⁴)`.** Bounding the D3/H4 sums at the
436
+ radii the periodic path uses was tried and reverted: it cost `2.5e-4` eV at
437
+ 375 atoms and bought nothing, the log-log slope staying at **1.959**. The sums
438
+ are `for i { for j { if r > cutoff { continue } } }`, so a radius bounds the
439
+ range and not the work — and 30 Bohr encloses ~2000 atoms of liquid water,
440
+ more than any system measured. See `examples/correction_cutoff.rs` and
441
+ `examples/correction_scaling.rs`.
442
+ - **Divide-and-conquer is at slope 1.16**, against an acceptance criterion of
443
+ 1.15, over 360 to 2880 atoms — 86× over full diagonalization at 2880 atoms, at
444
+ 28 µeV per atom, a figure flat in system size.
445
+ - **A cliff at the Γ-point margin.** Crossing `DEFAULT_SHORT_RANGE_CUTOFF`
446
+ (14 Bohr) steps a water box's energy by **35 eV** between 7.4080 and 7.4085 Å.
447
+ Documented behaviour rather than a defect — below the margin the SCF converges
448
+ cleanly to a well-defined wrong answer — but the size of the step is not
449
+ obvious from that sentence, and a test fixture of this crate's own was sitting
450
+ on the wrong side of it. See `examples/cell_continuity.rs`.
451
+
452
+ ### Two Berry-phase conventions worth writing down
453
+
454
+ Both were derived against this crate's own gauge rather than taken from a
455
+ published form, because getting either wrong returns a plausible number rather
456
+ than an error.
457
+
458
+ - **No closure factor on the last link.** `bloch_fock` carries the phase on the
459
+ lattice translation alone, so `H(k + G) = H(k)` exactly and the coefficients at
460
+ `k₀ + G` *are* those at `k₀`. An extra `e^{−iG·τ}` put fluorine's Born charge
461
+ at `+21.8 e` against a true `−0.33`.
462
+ - **The sign of the electronic term.** With `e^{−ib·τ}` in the overlap, the
463
+ textbook `−(e/Ω) φ a` counts that sign twice. Fixed by the single-orbital
464
+ limit; getting it wrong gives `+14.4`.
465
+
466
+ With both right, the Berry and CPHF Born charges differ by `0.147 e` — and
467
+ removing the intra-atomic `dd` moment the phase omits (`PM3_BORN_NO_DD=1`)
468
+ collapses that to `1.9e-4`. The two formalisms agree on everything except one
469
+ identified physical term.
470
+
471
+ ## 0.2.1
472
+
473
+ Phonons everywhere the Γ point already worked, an external electric field, and
474
+ the wavefunction outputs that go with it. Three defects that had shipped in
475
+ 0.2.0 are fixed; all of them were invisible at `q = 0`, which is where every
476
+ test that could have caught them ran.
477
+
478
+ ### Added
479
+
480
+ - **Analytic stress for a slab.** Every periodic dimensionality now reports one:
481
+ a chain its axis, a slab its two in-plane components, a crystal all nine, with
482
+ exact zeros in the non-periodic directions. `relax` relaxes the cell vectors
483
+ that exist and leaves a slab's vacuum thickness alone; only an isolated cell
484
+ refuses, having no strain rather than an underived derivative.
485
+ - **A uniform external electric field** (`Pm3Options::field`), molecular only —
486
+ energy, analytic gradient and analytic Hessian. Validated against MOPAC's own
487
+ `FIELD=` keyword to all eight digits MOPAC prints. Refused under periodic
488
+ boundary conditions, where `−f·r` is not lattice-periodic.
489
+ - **A shared dipole operator** (`pm3_rs::dipole`). The reported dipole and the
490
+ field coupling are the same matrix, which is what makes `μ = −∂E/∂F` hold by
491
+ construction rather than by coincidence.
492
+ - **Molden wavefunction output** (`pm3_rs::molden`, the CLI's `molden` command,
493
+ `write_molden` in both Python layers). The coefficients are the raw ZDO ones,
494
+ which is MOPAC's own `VECTORS`/`GRAPHF` convention and makes the comparison
495
+ against it direct; the Slater functions are expanded in Gaussians derived at
496
+ run time by a regularized linear least squares rather than transcribed from a
497
+ table, to a measured overlap deficit of `2e-5` at worst.
498
+ - **β orbitals on `Pm3Result`** (`mo_energies_beta`, `mo_coeff_beta`, `n_beta`),
499
+ which an unrestricted Molden file needs and which the UHF Hessian was
500
+ re-diagonalizing to recover.
501
+ - **Infrared intensities** (`pm3_rs::ir`): the dipole-derivative tensor
502
+ `∂μ/∂R` from **three** coupled-perturbed solves rather than `3N`, by the
503
+ interchange theorem, with the nuclear term the electronic part alone would
504
+ miss; and from it the per-mode spectrum in km/mol, translations and rotations
505
+ projected out of the mass-weighted modes. RHF and UHF, both finite-difference
506
+ checked. The km/mol conversion is computed from its SI inputs rather than
507
+ transcribed, and checked against the published 974.88.
508
+ - **Phonons for a spin-polarized cell.** The response is written over a list of
509
+ spin channels rather than as a pair of code paths: a closed shell is one
510
+ channel holding the total density at half exchange strength with two electrons
511
+ per state, an open shell two channels each holding its own at full strength
512
+ with one. Those are the same number when the spins are equal, which keeps the
513
+ restricted path at its old cost — one diagonalization per k-point, not two —
514
+ and pins it, every closed-shell value being unchanged. Validated against
515
+ central differences of the periodic UHF force, which shares none of the
516
+ response machinery.
517
+ - **The classical corrections at finite `q`.** `D(q)` carries the D3/H4/X terms
518
+ instead of refusing them. Their contribution is the bilinear form
519
+ `Σ_{T,T'} e^{−iq·T} e^{+iq·T'} ∂²E_cell/∂x_{(κ,T)}∂x_{(κ',T')}`, obtained by
520
+ scaling each cluster entry's displacement by its own weight and taking a
521
+ forward-mode second derivative of the whole cluster energy — never of a pair,
522
+ so D3's coordination-number coupling and H4's donor–hydrogen–acceptor triples
523
+ come along. Four real evaluations per entry, `Dual2` being real.
524
+ - **Every new feature reaches every API.** The external field, the phonons at a
525
+ wavevector, the band structure and the variable-cell relaxation are now
526
+ callable from `pm3_rs`, `pm3_rs.native`, the ASE calculator and (for the
527
+ field) the CLI's `--field`; `divide_and_conquer` gained the ASE accessor it
528
+ never had. The Python layers take the field in **volts per Angstrom**, the unit
529
+ MOPAC's own `FIELD=` keyword uses. `dipole`, `ir`, `molden` and `pbc::dfpt`
530
+ are re-exported from the crate root and pinned by `tests/api_surface.rs`,
531
+ which covered none of them.
532
+ - **The divide-and-conquer SCF's memory is linear in the system.** The eight
533
+ `nao × nao` matrices the loop held — two densities, two Fock matrices, four
534
+ workspaces — live on the sparsity pattern now. Every one of them was already
535
+ read back only through that pattern, and the subsystem gather looks exactly
536
+ where some subsystem holds both orbitals, so nothing outside it was ever
537
+ consulted: no number changes, and `a_reaching_buffer_reproduces_the_full_result_exactly`
538
+ still holds to the bit. The Fock build writes into the pattern directly instead
539
+ of filling an array to have most of it ignored. Measured on a chain of waters,
540
+ 8 → 128 molecules: dense grows 0.14 → 36.0 MiB, a log-log slope of exactly
541
+ 2.00; on the pattern, 0.141 → 5.06 MiB, slope **1.06**.
542
+ - **`pm3_rs::dipole` and `pbc::dfpt` reach Python.** `dipole(...)` returns the
543
+ operator, the centre of mass it is taken about, and the `3 × 3N` derivative
544
+ tensor — three coupled-perturbed solves, not `3N`, and no Hessian, which is
545
+ what separates it from `ir_spectrum`. `dynamical_matrix(...)` returns `D(q)`
546
+ itself rather than only the frequencies it is diagonalized to, with the
547
+ Hermitian defect it was assembled at. Both reach `pm3_rs`, `pm3_rs.native` and
548
+ the ASE calculator.
549
+ - **The heavy ASE accessors are cached.** They were already lazy — nothing but
550
+ energy, forces, charges and dipole is computed in a `calculate` cycle — but
551
+ each recomputed on every ask, so a caller who wanted frequencies and then an
552
+ infrared spectrum paid for two Hessians and got no warning. Each now keeps its
553
+ most recent result against the geometry and parameters it was computed at, and
554
+ the phonon and dynamical-matrix caches are keyed on the wavevector and mesh as
555
+ well, so a dispersion sweep neither reuses the wrong answer nor accumulates
556
+ every matrix it built.
557
+ - **A multipole tree for the isolated far field.** The point-multipole sum past
558
+ the 80 Bohr handover was quadratic in atoms; it now runs over an octree whose
559
+ nodes carry the combined moments of everything beneath them, so a distant group
560
+ is one term rather than many. The translation up the tree is exact — a node's
561
+ moments *are* its clouds', shifted — so the only error is the expansion's own
562
+ truncation, which a test checks against the definition rather than against the
563
+ recursion that produced it.
564
+
565
+ A node is accepted only when `d − s > MULTIPOLE_RADIUS`, so by the triangle
566
+ inequality every cloud inside it was already far by the pairwise rule and **the
567
+ near field is exactly the set it was**. The second condition is an *absolute*
568
+ error bound, not a Barnes–Hut opening angle: the pairwise rule it has to match
569
+ is absolute, and a fixed angle is far looser where it matters — `θ = 0.3` moved
570
+ the energy by 1.5 meV against a 50 µeV tolerance. Measured by counting terms
571
+ rather than by a clock, since this machine runs other work: over a block
572
+ growing 125 → 1000 clouds the far-field count grows with a log-log slope of
573
+ **1.58** against the pairwise sum's exact 2.00, doing 18% of the pairwise work
574
+ at the larger size.
575
+ - **An independent check on the phased lattice sum.** `ewald_phased` shipped in
576
+ 0.2.0 with four tests, all of which compare it against itself.
577
+ `ewald_reference::direct_phased_sum` compares it against a direct sum over
578
+ whole cells that shares none of its algebra — value, gradient and Hessian, to
579
+ better than `1e-6` at a generic interior wavevector.
580
+
581
+ - **Phonons off Γ in one and two dimensions.** The phased lattice sum, the phased
582
+ Parry slab and the phased direct chain now all exist, so a slab or a wire has a
583
+ dynamical matrix at any wavevector in its periodic subspace — a component along
584
+ a non-periodic axis is refused rather than quietly summed. `slab_kernel` gained
585
+ its second `z` derivative (and its first unit test), and `ewald_atom_hessian`
586
+ covers 1D and 2D through the phased sum at `q = 0` instead of a second
587
+ implementation.
588
+
589
+ ### Fixed
590
+
591
+ - **`D(q)` was not Hermitian**, by an amount that was exactly zero at `q = 0` and
592
+ grew linearly with `q` — a tenth of an eV/Bohr² out of twenty-five by
593
+ `q = (0.2, 0, 0)`. The first-order density's contribution to the multipole
594
+ charges read only the lower triangle and doubled it. That is the same number as
595
+ the symmetrized form for a ground-state density, which is real and symmetric,
596
+ and a different number for a complex first-order one; it also stopped the
597
+ charge map being the adjoint of the potential map, which is what Hermiticity
598
+ rests on. Every identity test in the file ran on the rigid-ion matrix, so none
599
+ of them could see it.
600
+ - **The response solve diverged at a general wavevector and said nothing.** It
601
+ reached `1e30` in its two hundred passes and returned what it was holding. Two
602
+ changes: it now refuses rather than returns, and it extrapolates over a history
603
+ instead of substituting the equation into itself. The response is linear, so
604
+ the plain iteration converges only where the spectral radius of `χ₀K` is below
605
+ one, and at a general `q` it is not; damping only rescales that eigenvalue.
606
+ Extrapolation solves the linear system on the Krylov subspace the history
607
+ spans, which does not care. Every wavevector probed now converges to `1e-10`.
608
+ - **The response summed the time-reversal-irreducible mesh.** The ground state may
609
+ — `P(−k) = P(k)*` — and the response may not: time reversal maps the coupled
610
+ pair `(k, k+q)` onto a pair at `−q`, so doubling the irreducible weights is the
611
+ wrong sum wherever `q ≠ −q`. The response now regenerates the full mesh, while
612
+ still sharing the potential the reduced SCF converged.
613
+ - **The external field was half-wired into three paths.** The open-shell
614
+ skeleton (`skeleton_fock_ov_spin`) never carried the field's first derivative,
615
+ so a UHF Hessian in a field came back symmetric, with six near-zero modes, and
616
+ short by the cross term `Tr[(∂P/∂R)(∂H'/∂R)]` — measured at `3e-3 eV/Bohr²`
617
+ for the methyl radical, sixteen times the finite-difference tolerance. The
618
+ divide-and-conquer energy omitted the field's nuclear half `−Σ_A Z_A R_A·f`,
619
+ and its gradient omitted `−q_A f`. `analytic_gradient` dropped the field
620
+ entirely. The screened long-range path and the periodic path now refuse a
621
+ field rather than ignore one — including on an isolated cell, where the field
622
+ is well-defined but that machinery still does not carry it.
623
+ - **Every periodic image was given the reference cell's coordination-number
624
+ response.** `cn[image] = cn[parent]` is exact at `Γ`, where all copies move
625
+ alike, and wrong under a phased displacement, where the image's neighbours
626
+ move by different amounts than its parent's do. It was therefore invisible to
627
+ every test that existed: at `q = 0` it is not an approximation. An image is
628
+ now given its own coordination number wherever the cluster is wide enough
629
+ around it for that number to be right — a triangle-inequality test against the
630
+ cluster radius — and the parent's beyond. Restoring the blanket copy fails the
631
+ supercell folding test by four thousand times its tolerance.
632
+ - **The phased reciprocal sum carried a conjugated phase**, `e^{+i(G+q)·d}` where
633
+ Poisson summation gives `e^{−i(G+q)·d}`, which made `D(q)` wrong in its
634
+ imaginary parts at a generic interior `q` in 3D. Every existing test was
635
+ structurally blind to it: the oracle contraction is identically real over `±d`
636
+ pairs, the folding test's `q = b/2` makes the shifted set negation-symmetric,
637
+ and the internal identities conjugate both halves together. Pinned now by the
638
+ α-independence of `Im Φ_q` and by `Φ_q(d+T₀) = e^{−iq·T₀} Φ_q(d)`.
639
+ - **The shipped Python is now ASCII.** Sixteen docstrings could not be printed on
640
+ a legacy-codepage console: the package imported and computed correctly, and
641
+ `help()` on it raised `UnicodeEncodeError` on Japanese, Chinese or Korean
642
+ Windows. Installing was never affected — verified by building the sdist and
643
+ installing it under a forced `cp932` locale — but a documented API surface was
644
+ unusable. Two tests keep it that way.
645
+ - **The divide-and-conquer radii disagreed between layers by a factor of 1.9.**
646
+ `DcOptions` defaults to 6.0 and 9.0 *Bohr*; the Python signature took Ångström
647
+ and defaulted to 6.0 and 9.0, so a caller who omitted the argument silently got
648
+ subsystems nearly twice the intended size. The CLI and `pm3_rs.native` were
649
+ right; the extension's own defaults are now the same radii.
650
+ - **`long_range_cutoff` was unreachable from `import pm3_rs`** — the whole
651
+ linear-scaling path existed and could not be switched on.
652
+ - **`phonons` alone had no `reference` argument**, so an open-shell cell could
653
+ not be asked for. ASE's forwarding is now by keyword, since adding the argument
654
+ immediately exposed a positional call handing `method` to `reference`.
655
+ - The type stub declared `optimize(max_iter, gtol)`, arguments that never
656
+ existed. A test now compares every stub signature against the extension, and
657
+ another asserts `pm3_rs.native` can pass every argument the extension accepts.
658
+
659
+ ## 0.2.0
660
+
661
+ Periodic boundary conditions. Molecular results are unchanged except for one
662
+ bug fix, noted below.
663
+
664
+ ### Added — periodic boundary conditions
665
+
666
+ - `Cell` on `Molecule`, covering 1D chains, 2D slabs, and 3D crystals. The
667
+ non-periodic directions never enter the measure, the reciprocal basis, or the
668
+ stress, so a slab's vacuum thickness cannot affect a result.
669
+ - Γ-point periodic SCF (`pbc::gamma::run_gamma`), RHF and UHF, with analytic
670
+ forces and analytic stress (`pbc::gradient::periodic_gradient`) and
671
+ fixed- or variable-cell relaxation (`pbc::optimize::relax`).
672
+ - Ewald electrostatics with the dimension dependence confined to the reciprocal
673
+ sum: 3D tinfoil, exact 2D Parry slab, a cell-grouped 1D direct sum, and a
674
+ plain owner-excluded pair sum at zero dimensions. Charged cells are supported
675
+ in every dimensionality — a uniform neutralizing background in 3D and 2D, a
676
+ neutralizing line charge in 1D — and the neutralizer's potential enters the
677
+ Fock matrix rather than only correcting the energy afterwards.
678
+ - D3, H4, X, and the simple hydrogen-bond correction are lattice summed,
679
+ including the D3 coordination number over images.
680
+ - `PeriodicResult::gamma_margin` reports the Γ-point validity condition — see
681
+ `docs/pbc.md`. A cell narrower than the exchange cutoff converges cleanly to
682
+ an answer that is wrong by tens of eV, and nothing else reveals it.
683
+ - **k-point sampling** (`pbc::kscf::run_kpoints`), RHF and UHF, neutral and
684
+ charged: Γ-centred Monkhorst–Pack meshes with exact time-reversal reduction,
685
+ a global Fermi level with optional Fermi–Dirac smearing, fixed or free
686
+ magnetization, and band structures along a path. Sampling more than one `k`
687
+ is what lets `P(0, T)` decay with `T`, which lifts the Γ-point cell-width
688
+ condition above.
689
+ - New `cmatrix` module: complex Hermitian matrices and eigensolver. ZDO makes
690
+ `S(k) = I`, so no generalized eigenproblem is needed.
691
+ - **Γ-point analytic Hessian and phonons** (`pbc::hessian`), with the Ewald
692
+ second derivative (`pbc::ewald_hessian`) and the periodic CPHF response. The
693
+ acoustic sum rule holds before enforcement, so enforcement cleans up rounding
694
+ rather than hiding a misplaced term.
695
+ - **Divide and conquer** (`dc`), molecular and Γ-point periodic, RHF and UHF:
696
+ Yang–Lee partitioning with a single global chemical potential, plus forces and
697
+ stress from the partitioned density.
698
+ - **Zero dimensions as a member of the same family** (`Cell::isolated`). An
699
+ isolated system is the case with no images, where the lattice sum is a plain
700
+ owner-excluded pair sum. Running a molecule through the periodic path gives it
701
+ the crystal's `O(N)` near field — neighbour-list pair tables plus a
702
+ point-charge model outside the cutoff — instead of a dense `O(N²)` pair cache.
703
+ It reproduces molecular PM3 to 3 µeV per atom with the default switch, and to
704
+ `1e-8` eV total with the switch pushed past the molecule; the error is
705
+ intensive, staying put from 12 to 288 atoms rather than accumulating.
706
+ - **A linear-scaling near field for molecular divide and conquer**
707
+ (`DcOptions::long_range_cutoff`, `None` by default). Same split at zero
708
+ dimensions. Measured at 960 atoms: 7.0× faster than full diagonalization
709
+ against 3.2× for the dense path, with the log-log slope down from 2.03 to
710
+ 1.27. Left off by default because the switch costs a measured 28 µeV per atom,
711
+ about a hundred times the divide-and-conquer truncation at the default buffer.
712
+ - **A multipole far field for isolated systems.** Beyond 80 Bohr two atoms now
713
+ interact through their net charge, dipole and second moment rather than through
714
+ all six hundred and twenty-five of their auxiliary site pairs, with the
715
+ potential carried back out to the sites by a Taylor expansion of matching
716
+ order. The self-consistent field also stops computing site gradients it never
717
+ reads. Together these took the Fock build at 960 atoms from 2.52 s to 1.22 s
718
+ and the whole run from 4.9 s to 3.0 s, with no measurable change in the answer
719
+ — the divide-and-conquer error stays at 28.2 µeV/atom, and `pbc::ewald`'s own
720
+ test puts the collapse at 8 µeV over forty-eight atoms.
721
+ `PM3_DC_PROFILE=1` prints the loop's split into Fock build, subsystem solves
722
+ and density assembly.
723
+ - **Analytic stress in 1D.** 1D is summed directly rather than through
724
+ reciprocal space, so its virial is the ordinary pair virial and comes free
725
+ with the gradients. Only the axial component exists; the projection onto the
726
+ strains a cell actually has now happens once, for every dimensionality, rather
727
+ than being left to each sum.
728
+ - **Charged 1D cells**, at Γ and at any k mesh, through a uniform neutralizing
729
+ line charge. The subtraction is in terms of the physical extent summed rather
730
+ than the image count — `H_N + ln(L/L₀)` with `L₀` a fixed reference — which is
731
+ what makes it size-consistent. `Q² H_N / L` alone gives each cell a stable,
732
+ plausible energy while a chain and its own doubled cell disagree by eV.
733
+ - **Sparkles, point atoms and `d` shells in the periodic path.** An atom with no
734
+ orbitals has no electronic multipoles, so its realization is the nucleus it
735
+ already has; a `d` element's realization comes from MNDO-d's own multipole
736
+ table, which has only `l ≤ 2` and so is carried in full rather than truncated.
737
+ (PM3 itself parameterizes all forty-two of its elements on an s/p basis, so
738
+ the `d` path is unreachable through the standard tables and its test builds
739
+ its own subject.)
740
+ - **DFPT at arbitrary `q`** (`pbc::dfpt`), with the phased lattice sum it needs
741
+ (`pbc::phased`): the dynamical matrix and phonon frequencies at any
742
+ wavevector, from the primitive cell, at a cost that does not depend on `q`.
743
+ The electrons' response is included, and it is most of the answer — on water
744
+ it turns a rigid-ion force constant of 5.96 eV/Bohr² into 0.56.
745
+ `rigid_ion_dynamical_matrix` gives the fixed-density part alone.
746
+
747
+ Validated at two levels. `D(0)` against `pbc::hessian::periodic_hessian`, an
748
+ independent implementation with no phases in it, itself checked against finite
749
+ differences; and `D(q)` by folding, a doubled cell's Γ-point force constants
750
+ holding the primitive cell's `D(0)` and `D(zone boundary)` between them. Each
751
+ constituent is separately checked against something built differently, which
752
+ is what made three real bugs findable rather than merely visible: a lattice
753
+ sum that skipped same-atom images, a phased kernel with no Ewald self term,
754
+ and an exchange handed a total density where it wanted a spin one.
755
+
756
+ The response samples one k-point, `Γ`, paired with `q` — the sampling the
757
+ ground state used, carrying the same `gamma_margin` condition. 3D, closed
758
+ shell, plain PM3.
759
+ - New `docs/pbc.md` and `docs/divide-and-conquer.md`.
760
+
761
+ ### Added — interfaces
762
+
763
+ - Python: `periodic_single_point`, `periodic_forces`, `phonons` and
764
+ `divide_and_conquer`, all taking a cell in Ångström.
765
+ - ASE: the calculator switches to the periodic path from `atoms.pbc`, adds
766
+ `stress` to `implemented_properties` (6-component Voigt, eV/ų) and gains
767
+ `get_phonons`. A molecule or a slab raises on `get_stress()` rather than
768
+ returning zeros, because zeros would be a claim rather than an absence.
769
+ - CLI: `--cell`, `--pbc`, `--kpts`, `--dc`, `--dc-core`, and the `stress`,
770
+ `phonons` and `bands` subcommands. A Γ-point run that violates the validity
771
+ condition prints a warning naming it.
772
+ - Packaging: PEP 639 licence metadata, classifiers, project URLs, keywords,
773
+ a `py.typed` marker and `_native.pyi` stubs, and GitHub Actions workflows for
774
+ CI and for wheels published through PyPI Trusted Publishing.
775
+ - **`pip install` puts the `pm3-rs` command on your path.** The CLI moved out of
776
+ `src/bin` and into the library, taking its argument vector rather than reading
777
+ the process environment, so a PyO3 wrapper can hand it `sys.argv`. `pip` and
778
+ `cargo install` give the identical interface rather than two that drift.
779
+ Verified by installing the source distribution into a clean virtualenv,
780
+ compiling from scratch, and running the installed command.
781
+
782
+ ### Fixed
783
+
784
+ - **XYZ files with a byte-order mark.** Notepad and PowerShell's
785
+ `-Encoding utf8` both put one in front of the atom count, which produced
786
+ `invalid XYZ atom count: 3` on a file whose first line was visibly `3`. A
787
+ leading mark is now stripped; a second one is still an error, because that is a
788
+ malformed file rather than a Windows editor.
789
+ - **Charged-system dipole origin.** The dipole was referenced to the coordinate
790
+ origin rather than to the centre of mass, so for any system with a net charge
791
+ it depended on where the molecule sat: translating NH₄⁺ by 5 Å moved its
792
+ dipole from 0 to 24.02 D. It now matches MOPAC's `dipole.F90` (centre of mass,
793
+ with `+`/`−` point atoms carrying zero mass) for all 60 oracle cases to ~1e-6 D.
794
+ Neutral systems are unaffected, the dipole being origin-independent there.
795
+ - `erfc` was computed as `1 − erf`, which cancels catastrophically: 1.16e-8
796
+ relative error at `x = 3.9`. It now uses a continued fraction above `x = 2`,
797
+ giving 3.9e-14 against scipy over 157 points. (New code only; no molecular
798
+ result used `erfc`.)
799
+ - The DIIS coefficient solve normalizes its Gram matrix before the pivot test.
800
+ The threshold was absolute while `⟨E_i,E_j⟩ ~ ‖E‖²` shrinks quadratically, so
801
+ a converging run could be handed a matrix of ~1e-10 entries and get back
802
+ wildly amplified coefficients — DIIS converging nicely and then walking back
803
+ out into a limit cycle. The coefficients are invariant under the scaling, so
804
+ no converged result changes.
805
+
806
+ - **The Γ-point periodic energy belonged to no state.** It was reported as
807
+ `½(P·H + P·F)` with `F` the *extrapolated* Fock — a combination of history
808
+ matrices — paired with the freshly diagonalized density, while the density
809
+ actually returned was the damped and extrapolated one. Three different
810
+ objects. The convergence test could not see it: a stable set of DIIS weights
811
+ makes a wrong energy stop moving as convincingly as a right one. Worst case
812
+ measured, 1.03 eV on a chain of 92 water molecules; usually far below
813
+ tolerance, which is why it survived. The k-point path was checked for the same
814
+ defect and does not have it — its accelerator extrapolates the density, not
815
+ the Fock.
816
+ - **The `B` auxiliary integrals lost seven digits just above `|x| = 0.5`.**
817
+ MOPAC's closed-form recursion multiplies its cancellation by `k/|x|` at every
818
+ step, and its power series stops at `0.5`; at `x = −0.51`, `B₉` was off by a
819
+ relative `5e-7` against the integral itself. Below `1e-6` the `x → 0` branch
820
+ returned constants, so a differentiating scalar got a zero derivative where
821
+ `dB₁/dx = −2/3`. Both go away by summing the defining series directly out to
822
+ `|x| = 3` — absolutely convergent, every term independent of every other,
823
+ exact at `x = 0` including its derivative — and the `x → 0` special case
824
+ disappears rather than being widened. Every frozen MOPAC value is unchanged.
825
+ - **The Slater overlap went NaN beyond about 500 Bohr.** `A_k` carries
826
+ `e^{−r(ζ_a+ζ_b)/2}` and `B_k` carries `e^{+r|ζ_a−ζ_b|/2}`; the overlap is their
827
+ product, which is tiny, but the two factors separately are `0` and `∞`, and
828
+ `B` overflows first. For an O–H pair that happens at 501 Bohr, and `0 · ∞` then
829
+ propagated silently through `H_core`. This affects **any** calculation
830
+ containing a 500 Bohr separation, not only a periodic or partitioned one; it
831
+ surfaced here because divide-and-conquer was the first thing run on a system
832
+ that large. The overlap is now cut where its own decaying factor is below
833
+ `1e-130`, so no overlap that could matter is affected and every frozen MOPAC
834
+ value is unchanged.
835
+
836
+ ### Performance
837
+
838
+ - **The periodic SCF now uses the molecular path's accelerator.** Plain CDIIS
839
+ interpolates the Fock with unconstrained weights and has no notion of the
840
+ energy going down, so only damping held it and how much damping is enough
841
+ grows with the system: on a chain of identical waters it converged at 56
842
+ molecules, failed at 58, converged again at 66, then failed at every size
843
+ beyond. Restricted periodic runs now use A-DIIS until the commutator is small
844
+ and CDIIS after, sharing `crate::scf`'s history rather than a second copy.
845
+ Every size converges, in 25 iterations where damped CDIIS needed 110.
846
+ - **The Ewald sum caches everything that depends on the geometry** — the
847
+ reciprocal enumeration, the neighbour list, and the `cos(G·r)`/`sin(G·r)` of
848
+ every site against every `G`. An SCF changes only the charges. Capped at
849
+ 256 MiB, above which the trigonometry is recomputed.
850
+ - **Divide and conquer no longer does `O(N²)` work on its own structural
851
+ zeros.** A partitioned density is zero wherever no subsystem holds both
852
+ orbitals — the approximation the method makes, not a rounding effect. DIIS,
853
+ damping, the RMS change and the energy trace now run over a recorded sparsity
854
+ pattern, and every matrix the loop touches is allocated once. At 960 atoms the
855
+ DIIS history alone fell from 7.17 s to 0.19 s and the whole run from 25.5 s to
856
+ 6.5 s, with the converged energies unchanged to every printed digit.
857
+ - **The ASE calculator computes forces unasked.** ASE requests one property at a
858
+ time and re-enters `calculate` for each, so naming only what was asked
859
+ converged the same SCF two or three times per MD step. With the Ewald cache, a
860
+ periodic single point went from 250 ms to 53 ms and an MD step from ~520 ms to
861
+ 74 ms.
862
+ - Periodic SCF: CDIIS on the `[F, P]` commutator, replacing plain damping
863
+ (about 38 iterations → a dozen, each costing a full lattice sum).
864
+ - Periodic UHF starts from core-Hamiltonian orbitals occupied to the two aufbau
865
+ counts rather than a spin-scaled atomic-density guess. The scaled guess starts
866
+ inside the spin-symmetric subspace, which for the methyl radical contains a
867
+ stationary point 4.8 eV above the UHF minimum: damping needed ~25 wasted
868
+ cycles to escape it and CDIIS converged straight onto it. Methyl in a cell
869
+ now takes 9 iterations rather than 96, and reaches the correct solution.
870
+ - The Ewald reciprocal sums enumerate one member of each `±G` pair and double.
871
+ Every quantity they produce is even under `G → −G`.
872
+
873
+ ## 0.1.2
874
+
875
+ Performance and memory release. No change to any computed PM3 quantity: the
876
+ MOPAC v23.2.5 oracle regressions (heats of formation, charges, gradients,
877
+ optimized geometries, frequencies, special atoms, Sparkles) are unchanged.
878
+
879
+ ### Performance
880
+
881
+ - The two-center Fock build — the `O(N²)` part of every SCF and CPHF iteration —
882
+ now runs batched-parallel over atom pairs instead of in a single serial loop.
883
+ - The two-center Coulomb term is contracted as two packed mat-vecs over the
884
+ `(μν)`/`(λσ)` orbital-pair indices rather than a four-index loop: 100 instead
885
+ of 256 multiply-adds per sp/sp pair, with identical arithmetic.
886
+ - The SCF accelerator keeps its `⟨E_i,E_j⟩` and `⟨D_i,F_j⟩` Gram matrices
887
+ incrementally, evaluating only the new row and column each iteration. The
888
+ A-DIIS difference matrices `D_i − D_n` / `F_j − F_n` are no longer
889
+ materialized, removing `2 × depth` full `nao × nao` temporaries per iteration.
890
+ - `[F,P]` is formed with one matrix product instead of two (`F` and `P` are
891
+ symmetric, so `PF = (FP)ᵀ`), and is skipped entirely when no accelerator runs.
892
+ - The two-electron pair table is a single flat buffer instead of a `Vec<Vec<_>>`,
893
+ removing one heap allocation per packed row and making each row contiguous.
894
+ - The classical-correction (D3/H4/X) Hessian off-diagonal loop runs on rayon.
895
+ - Elementwise reductions (`frobenius_dot`, RMS density change, history
896
+ combination) go parallel above 2^18 elements.
897
+ - `symmetric_eigen` skips building a permutation when `faer` already returns
898
+ ascending eigenvalues.
899
+ - 900-atom water cluster (nao = 1800), 16 cores: 35.7 s → 17.7 s wall.
900
+
901
+ ### Memory
902
+
903
+ - The pair cache no longer retains the electron–core attraction blocks
904
+ `e1b`/`e2a` (2 × 81 `f64` per pair). They are consumed while `H_core` is
905
+ assembled and never referenced again; holding them cost 1.3 KiB per pair
906
+ (3.7 GiB for a 2400-atom system) for no benefit.
907
+ - New `Pm3Options::integral_memory_mb` (env `PM3_MAX_PAIR_CACHE_MB`, default
908
+ 4096 MiB): the `O(N²)` pair cache size is computed in closed form before the
909
+ allocation and reported as a `ResourceLimit` error if it exceeds the budget.
910
+ - New `Pm3Options::scf_memory_mb` (default 512 MiB): bounds the SCF accelerator
911
+ history. The DIIS depth is reduced to fit, and A-DIIS degrades to CDIIS (which
912
+ needs no density history) rather than exceeding the budget.
913
+ - 900-atom water cluster peak resident set: ≈ 1.4 GiB → 0.75 GiB.
914
+
915
+ ### Documented-API verification
916
+
917
+ Every code block and stated guarantee in `README.md`, `docs/rust-api.md`, and
918
+ `docs/python-api.md` is now executed as a test, and the CLI is exercised over
919
+ every documented subcommand, flag, and error path.
920
+
921
+ - `tests/api_surface.rs` (new, 15 tests) — the Rust surface: constructors,
922
+ `Pm3Options` fields and defaults, the `Pm3Result`/`GradientResult`/
923
+ `VibrationalModes`/`OptResult` field lists, unit conventions, `Variant::parse`,
924
+ reference selection, the memory budgets, and the documented error variants.
925
+ - `tests/test_python_api.py` (new, 19 tests) — `pm3_rs.native` dict keys and
926
+ Hartree↔eV / Bohr↔Å conversions, the `pm3_rs.ase.PM3` calculator (ASE units,
927
+ lazy Hessian, every accessor), and both documented example blocks.
928
+
929
+ Fixed along the way:
930
+
931
+ - **`pm3_rs.ase.PM3`**: every accessor (`get_potential_energy`, `get_forces`,
932
+ `get_gradient`, `get_hessian`, `get_frequencies`) raised
933
+ `AttributeError: 'NoneType' object has no attribute 'get_atomic_numbers'` when
934
+ called with no argument before the calculator had been bound to a structure.
935
+ They now raise a `RuntimeError` that names the three ways to fix it.
936
+ - `docs/python-api.md`: the cation example was labelled "forced UHF doublet" but
937
+ passed `multiplicity=1` (NH4+ is a closed-shell singlet), and its
938
+ `nh4_positions` was never defined, so the block could not be run as printed.
939
+ Corrected, given a runnable geometry, and extended with an open-shell example.
940
+ - `docs/python-api.md`: documented what `atoms=None` means for the ASE
941
+ accessors — assigning `atoms.calc = PM3()` does not bind the structure.
942
+
943
+ ### Documentation
944
+
945
+ - Removed the claims that this crate is derived from or structured after sibling
946
+ Rust projects; the PM3 Hamiltonian, parameters, and derivations are documented
947
+ against MOPAC v23.2.5 and the primary literature.
948
+ - `third_party/pyseqm/NOTICE` added — `THIRD_PARTY_NOTICES.md` referenced a
949
+ `third_party/pyseqm/LICENSE` that was not present.
950
+ - Corrected the MOPAC reference values quoted in the `scf.rs` unit-test comments
951
+ for water and the methyl radical; they did not match the asserted PM3 values.
952
+ - `docs/rust-api.md`: documented the three memory budgets and corrected the
953
+ description of the Hessian `step` argument.
954
+
955
+ ## 0.1.1
956
+
957
+ Initial `pm3-rs` release, backed by the PM3 Hamiltonian and the MOPAC v23.2.5
958
+ PM3 parameter tables.
959
+
960
+ - Rust implementation; linear algebra via `faer`, no BLAS/LAPACK dependency.
961
+ - RHF/UHF energies, heats of formation, charges, dipoles, analytic gradients,
962
+ CPHF/UCPHF Hessians, optimization, and frequencies.
963
+ - PM3-D3, PM3-D3H4, and PM3-D3H4X correction variants.
964
+ - MOPAC special atoms `Cb`, `+`, `-`, and La-Lu trivalent Sparkles.
965
+ - Rust library `pm3_rs`, CLI `pm3_rs_cli`, and Python distribution
966
+ `pm3-rs-python` with native and ASE APIs.
967
+ - MOPAC v23.2.5 oracle regressions for closed-shell molecules, an open-shell
968
+ radical, a Sparkle complex, point charges, gradients, optimization, and
969
+ frequencies.
970
+ - Configurable Hessian workspace budget and bounded integral-cache construction.
971
+ - Release profile uses fat LTO with one code-generation unit.