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