gfn2-rs-python 0.4.5__tar.gz → 0.6.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. gfn2_rs_python-0.6.1/CHANGELOG.md +2064 -0
  2. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/Cargo.lock +1 -1
  3. gfn2_rs_python-0.6.1/Cargo.toml +142 -0
  4. gfn2_rs_python-0.6.1/LICENSES/Apache-2.0.txt +202 -0
  5. gfn2_rs_python-0.6.1/LICENSES/BSD-3-Clause.txt +11 -0
  6. gfn2_rs_python-0.6.1/LICENSES/CC-BY-4.0.txt +156 -0
  7. gfn2_rs_python-0.6.1/LICENSES/GPL-3.0-or-later.txt +674 -0
  8. gfn2_rs_python-0.6.1/LICENSES/LGPL-3.0-or-later.txt +165 -0
  9. gfn2_rs_python-0.6.1/LICENSES/LLVM-exception.txt +15 -0
  10. gfn2_rs_python-0.6.1/LICENSES/MIT.txt +18 -0
  11. gfn2_rs_python-0.6.1/LICENSES/MPL-2.0.txt +373 -0
  12. gfn2_rs_python-0.6.1/LICENSES/Unicode-3.0.txt +39 -0
  13. gfn2_rs_python-0.6.1/LICENSES/Zlib.txt +11 -0
  14. gfn2_rs_python-0.6.1/NOTICE +188 -0
  15. gfn2_rs_python-0.6.1/PKG-INFO +514 -0
  16. gfn2_rs_python-0.6.1/README.md +466 -0
  17. gfn2_rs_python-0.6.1/THIRD_PARTY_LICENSES_RUST.md +12302 -0
  18. gfn2_rs_python-0.6.1/THIRD_PARTY_NOTICES.md +470 -0
  19. gfn2_rs_python-0.6.1/data/param_gfn2-xtb.txt +1601 -0
  20. gfn2_rs_python-0.6.1/data/param_spin_gfn2.txt +161 -0
  21. gfn2_rs_python-0.6.1/docs/README.md +42 -0
  22. gfn2_rs_python-0.6.1/docs/measurements.md +1152 -0
  23. gfn2_rs_python-0.6.1/docs/nn-xtb.md +1094 -0
  24. gfn2_rs_python-0.6.1/docs/pip_reachability.md +245 -0
  25. gfn2_rs_python-0.6.1/docs/python-api.md +948 -0
  26. gfn2_rs_python-0.6.1/docs/rust-api.md +1284 -0
  27. gfn2_rs_python-0.6.1/docs/whats-new-0.6.1.md +491 -0
  28. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/ase_singlepoint.py +2 -2
  29. gfn2_rs_python-0.6.1/examples/nn_bench.rs +302 -0
  30. gfn2_rs_python-0.6.1/models/README.md +312 -0
  31. gfn2_rs_python-0.6.1/pyproject.toml +120 -0
  32. gfn2_rs_python-0.6.1/python/gfn2_rs/__init__.py +152 -0
  33. gfn2_rs_python-0.6.1/python/gfn2_rs/_cli.py +27 -0
  34. gfn2_rs_python-0.6.1/python/gfn2_rs/ase.py +889 -0
  35. gfn2_rs_python-0.6.1/python/gfn2_rs/models/README.md +41 -0
  36. gfn2_rs_python-0.6.1/python/gfn2_rs/models/nnxtb-v0.6.1-provisional.gfn2nn +0 -0
  37. gfn2_rs_python-0.6.1/scripts/collect_rust_licenses.py +265 -0
  38. gfn2_rs_python-0.6.1/scripts/extract_lnxtb_params.py +361 -0
  39. gfn2_rs_python-0.6.1/scripts/make_release_zip.py +245 -0
  40. gfn2_rs_python-0.6.1/scripts/make_tblite_oracle.py +546 -0
  41. gfn2_rs_python-0.6.1/scripts/rmd17_relative_energies.py +227 -0
  42. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/aes.rs +355 -101
  43. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/basis.rs +42 -5
  44. gfn2_rs_python-0.6.1/src/bin/cubic_check.rs +1051 -0
  45. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/gfn2_check_elements.rs +6 -7
  46. gfn2_rs_python-0.6.1/src/bin/gfn2_rs.rs +9 -0
  47. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/gradient_check.rs +8 -8
  48. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/hessian_check.rs +85 -7
  49. gfn2_rs_python-0.6.1/src/bin/nn_eval.rs +10 -0
  50. gfn2_rs_python-0.6.1/src/bin/nn_train.rs +10 -0
  51. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/param_deriv.rs +22 -9
  52. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/stress_check.rs +8 -8
  53. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/water_cluster_opt.rs +6 -7
  54. gfn2_rs_python-0.6.1/src/cli/main_cli.rs +4735 -0
  55. gfn2_rs_python-0.6.1/src/cli/mod.rs +12 -0
  56. gfn2_rs_python-0.6.1/src/cli/nn_eval_cli.rs +1639 -0
  57. gfn2_rs_python-0.6.1/src/cli/nn_train_cli.rs +1052 -0
  58. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/coordination.rs +277 -6
  59. gfn2_rs_python-0.6.1/src/cphf/bloch.rs +3347 -0
  60. gfn2_rs_python-0.6.1/src/cphf/gamma.rs +1604 -0
  61. gfn2_rs_python-0.6.1/src/cphf/mod.rs +1153 -0
  62. gfn2_rs_python-0.6.1/src/cphf/rhs_gamma.rs +1000 -0
  63. gfn2_rs_python-0.6.1/src/cphf/solver.rs +944 -0
  64. gfn2_rs_python-0.6.1/src/cphf/spin_bloch.rs +1114 -0
  65. gfn2_rs_python-0.6.1/src/cphf/spin_gamma.rs +1617 -0
  66. gfn2_rs_python-0.6.1/src/cphf/tests.rs +3854 -0
  67. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/d4_reference.rs +425 -5
  68. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/data_tables.rs +12 -3
  69. gfn2_rs_python-0.6.1/src/dc.rs +2847 -0
  70. gfn2_rs_python-0.6.1/src/defaults.rs +217 -0
  71. gfn2_rs_python-0.6.1/src/dfpt/bands.rs +378 -0
  72. gfn2_rs_python-0.6.1/src/dfpt/cn.rs +135 -0
  73. gfn2_rs_python-0.6.1/src/dfpt/complex.rs +306 -0
  74. gfn2_rs_python-0.6.1/src/dfpt/kernel.rs +485 -0
  75. gfn2_rs_python-0.6.1/src/dfpt/perturbation.rs +370 -0
  76. gfn2_rs_python-0.6.1/src/dfpt/q_tests.rs +1274 -0
  77. gfn2_rs_python-0.6.1/src/dfpt/response.rs +346 -0
  78. gfn2_rs_python-0.6.1/src/dfpt/skeleton.rs +478 -0
  79. gfn2_rs_python-0.6.1/src/dfpt/solver.rs +335 -0
  80. gfn2_rs_python-0.6.1/src/dfpt.rs +1709 -0
  81. gfn2_rs_python-0.6.1/src/dispersion.rs +3795 -0
  82. gfn2_rs_python-0.6.1/src/eeqbc.rs +1339 -0
  83. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/eeqbc_mctc_data.rs +21 -0
  84. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/eeqbc_params.rs +22 -0
  85. gfn2_rs_python-0.6.1/src/electronic/bloch.rs +3761 -0
  86. gfn2_rs_python-0.6.1/src/electronic/frontier.rs +617 -0
  87. gfn2_rs_python-0.6.1/src/electronic/gamma.rs +1068 -0
  88. gfn2_rs_python-0.6.1/src/electronic/lc.rs +877 -0
  89. gfn2_rs_python-0.6.1/src/electronic/mod.rs +782 -0
  90. gfn2_rs_python-0.6.1/src/electronic/scc_common.rs +3181 -0
  91. gfn2_rs_python-0.6.1/src/electronic/scc_strategy.rs +1084 -0
  92. gfn2_rs_python-0.6.1/src/electronic/spinpol.rs +1394 -0
  93. gfn2_rs_python-0.6.1/src/electronic/tests.rs +773 -0
  94. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/gradients.rs +36 -14
  95. gfn2_rs_python-0.6.1/src/hessian/aes.rs +677 -0
  96. gfn2_rs_python-0.6.1/src/hessian/aes_potential_deriv.rs +1001 -0
  97. gfn2_rs_python-0.6.1/src/hessian/classical.rs +543 -0
  98. gfn2_rs_python-0.6.1/src/hessian/cphf_bloch.rs +618 -0
  99. gfn2_rs_python-0.6.1/src/hessian/cphf_gamma.rs +797 -0
  100. gfn2_rs_python-0.6.1/src/hessian/d4.rs +1565 -0
  101. gfn2_rs_python-0.6.1/src/hessian/d4_potential_deriv.rs +439 -0
  102. gfn2_rs_python-0.6.1/src/hessian/mod.rs +1809 -0
  103. gfn2_rs_python-0.6.1/src/hessian/perf.rs +330 -0
  104. gfn2_rs_python-0.6.1/src/hessian/response.rs +1758 -0
  105. gfn2_rs_python-0.6.1/src/hessian/scc_shell.rs +646 -0
  106. gfn2_rs_python-0.6.1/src/hessian/spin_response.rs +690 -0
  107. gfn2_rs_python-0.6.1/src/hessian/tests.rs +9260 -0
  108. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/hessian_pulay.rs +177 -166
  109. gfn2_rs_python-0.6.1/src/integrals.rs +1690 -0
  110. gfn2_rs_python-0.6.1/src/integrals_third_derivatives.rs +818 -0
  111. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/ko_ewald.rs +927 -50
  112. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/kpoints.rs +209 -51
  113. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/lattice.rs +114 -32
  114. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/lib.rs +61 -13
  115. gfn2_rs_python-0.6.1/src/licenses.rs +128 -0
  116. gfn2_rs_python-0.6.1/src/linalg.rs +557 -0
  117. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/model.rs +72 -15
  118. gfn2_rs_python-0.6.1/src/molden.rs +1126 -0
  119. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/multipole.rs +142 -15
  120. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/native_gradient.rs +745 -262
  121. gfn2_rs_python-0.6.1/src/nn/calculator.rs +2112 -0
  122. gfn2_rs_python-0.6.1/src/nn/cg.rs +415 -0
  123. gfn2_rs_python-0.6.1/src/nn/dataset.rs +2167 -0
  124. gfn2_rs_python-0.6.1/src/nn/distill.rs +1121 -0
  125. gfn2_rs_python-0.6.1/src/nn/force_adjoint.rs +1893 -0
  126. gfn2_rs_python-0.6.1/src/nn/freq_modes.rs +194 -0
  127. gfn2_rs_python-0.6.1/src/nn/heads.rs +395 -0
  128. gfn2_rs_python-0.6.1/src/nn/hessian_aes_family.rs +1676 -0
  129. gfn2_rs_python-0.6.1/src/nn/hessian_analytic.rs +4526 -0
  130. gfn2_rs_python-0.6.1/src/nn/hessian_d4_family.rs +1160 -0
  131. gfn2_rs_python-0.6.1/src/nn/hessian_exp_family.rs +2134 -0
  132. gfn2_rs_python-0.6.1/src/nn/mace.rs +1748 -0
  133. gfn2_rs_python-0.6.1/src/nn/mod.rs +39 -0
  134. gfn2_rs_python-0.6.1/src/nn/model_io.rs +1084 -0
  135. gfn2_rs_python-0.6.1/src/nn/optim.rs +453 -0
  136. gfn2_rs_python-0.6.1/src/nn/overlay.rs +834 -0
  137. gfn2_rs_python-0.6.1/src/nn/param_grad.rs +2053 -0
  138. gfn2_rs_python-0.6.1/src/nn/paramset.rs +147 -0
  139. gfn2_rs_python-0.6.1/src/nn/radial.rs +428 -0
  140. gfn2_rs_python-0.6.1/src/nn/rng.rs +359 -0
  141. gfn2_rs_python-0.6.1/src/nn/spherical.rs +660 -0
  142. gfn2_rs_python-0.6.1/src/nn/tape.rs +2820 -0
  143. gfn2_rs_python-0.6.1/src/nn/tda_gradient.rs +229 -0
  144. gfn2_rs_python-0.6.1/src/nn/third_derivative.rs +1349 -0
  145. gfn2_rs_python-0.6.1/src/nn/third_repulsion.rs +578 -0
  146. gfn2_rs_python-0.6.1/src/nn/train.rs +4431 -0
  147. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/occupations.rs +38 -0
  148. gfn2_rs_python-0.6.1/src/optimizer.rs +1444 -0
  149. gfn2_rs_python-0.6.1/src/params.rs +1695 -0
  150. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/profile.rs +90 -1
  151. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/python.rs +1358 -41
  152. gfn2_rs_python-0.6.1/src/scc_accel.rs +620 -0
  153. gfn2_rs_python-0.6.1/src/spin.rs +921 -0
  154. gfn2_rs_python-0.6.1/src/stability.rs +342 -0
  155. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/system.rs +6 -0
  156. gfn2_rs_python-0.6.1/src/tda/davidson.rs +461 -0
  157. gfn2_rs_python-0.6.1/src/tda/gamma_states.rs +1010 -0
  158. gfn2_rs_python-0.6.1/src/tda/gradient.rs +2764 -0
  159. gfn2_rs_python-0.6.1/src/tda/kpoint_states.rs +1601 -0
  160. gfn2_rs_python-0.6.1/src/tda/mod.rs +517 -0
  161. gfn2_rs_python-0.6.1/src/tda/sigma.rs +116 -0
  162. gfn2_rs_python-0.6.1/src/tda/tests.rs +483 -0
  163. gfn2_rs_python-0.6.1/src/term_physics.rs +704 -0
  164. gfn2_rs_python-0.6.1/src/third_derivatives/aes3.rs +761 -0
  165. gfn2_rs_python-0.6.1/src/third_derivatives/aes_response3.rs +891 -0
  166. gfn2_rs_python-0.6.1/src/third_derivatives/classical/tests.rs +422 -0
  167. gfn2_rs_python-0.6.1/src/third_derivatives/classical.rs +1808 -0
  168. gfn2_rs_python-0.6.1/src/third_derivatives/cphf3.rs +1131 -0
  169. gfn2_rs_python-0.6.1/src/third_derivatives/h0_pref3.rs +883 -0
  170. gfn2_rs_python-0.6.1/src/third_derivatives/jet3.rs +1061 -0
  171. gfn2_rs_python-0.6.1/src/third_derivatives/mod.rs +544 -0
  172. gfn2_rs_python-0.6.1/src/third_derivatives/response3/tests.rs +2204 -0
  173. gfn2_rs_python-0.6.1/src/third_derivatives/response3.rs +2780 -0
  174. gfn2_rs_python-0.6.1/src/third_derivatives/tests.rs +491 -0
  175. gfn2_rs_python-0.6.1/src/vibrations.rs +1051 -0
  176. gfn2_rs_python-0.6.1/src/wire_sum.rs +788 -0
  177. gfn2_rs_python-0.6.1/tests/ORACLE_NOTES.md +364 -0
  178. gfn2_rs_python-0.6.1/tests/attribution.rs +462 -0
  179. gfn2_rs_python-0.6.1/tests/cli_matrix.rs +524 -0
  180. gfn2_rs_python-0.6.1/tests/common/mod.rs +192 -0
  181. gfn2_rs_python-0.6.1/tests/identities.rs +1652 -0
  182. gfn2_rs_python-0.6.1/tests/nn_freq_route_cost.rs +466 -0
  183. gfn2_rs_python-0.6.1/tests/oracle/tblite_data.rs +2843 -0
  184. gfn2_rs_python-0.6.1/tests/oracle/xtb_data.rs +1511 -0
  185. gfn2_rs_python-0.6.1/tests/oracle_gfn2.rs +625 -0
  186. gfn2_rs_python-0.6.1/tests/oracle_tblite.rs +209 -0
  187. gfn2_rs_python-0.6.1/tests/physics.rs +2575 -0
  188. gfn2_rs_python-0.6.1/tests/test_bundled_model.py +297 -0
  189. gfn2_rs_python-0.6.1/tests/test_cli_scope_runtime.py +437 -0
  190. gfn2_rs_python-0.6.1/tests/test_console_scripts.py +100 -0
  191. gfn2_rs_python-0.6.1/tests/test_python_api.py +795 -0
  192. gfn2_rs_python-0.6.1/tests/test_wheel_licenses.py +185 -0
  193. gfn2_rs_python-0.4.5/Cargo.toml +0 -66
  194. gfn2_rs_python-0.4.5/PKG-INFO +0 -193
  195. gfn2_rs_python-0.4.5/README.md +0 -173
  196. gfn2_rs_python-0.4.5/THIRD_PARTY_NOTICES.md +0 -15
  197. gfn2_rs_python-0.4.5/docs/python-api.md +0 -249
  198. gfn2_rs_python-0.4.5/docs/rust-api.md +0 -210
  199. gfn2_rs_python-0.4.5/pyproject.toml +0 -33
  200. gfn2_rs_python-0.4.5/python/gfn2_rs/__init__.py +0 -70
  201. gfn2_rs_python-0.4.5/python/gfn2_rs/ase.py +0 -339
  202. gfn2_rs_python-0.4.5/src/bin/gfn2_rs.rs +0 -2326
  203. gfn2_rs_python-0.4.5/src/cphf.rs +0 -8701
  204. gfn2_rs_python-0.4.5/src/dispersion.rs +0 -2156
  205. gfn2_rs_python-0.4.5/src/eeqbc.rs +0 -746
  206. gfn2_rs_python-0.4.5/src/electronic.rs +0 -9985
  207. gfn2_rs_python-0.4.5/src/hessian.rs +0 -14048
  208. gfn2_rs_python-0.4.5/src/integrals.rs +0 -485
  209. gfn2_rs_python-0.4.5/src/linalg.rs +0 -323
  210. gfn2_rs_python-0.4.5/src/optimizer.rs +0 -572
  211. gfn2_rs_python-0.4.5/src/params.rs +0 -928
  212. gfn2_rs_python-0.4.5/src/spin.rs +0 -380
  213. gfn2_rs_python-0.4.5/src/tda.rs +0 -5224
  214. gfn2_rs_python-0.4.5/tests/test_cli_scope_runtime.py +0 -261
  215. gfn2_rs_python-0.4.5/tests/test_python_api.py +0 -159
  216. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/LICENSE +0 -0
  217. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/ferrocene.xyz +0 -0
  218. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/ferrocene_pbc_20a.xyz +0 -0
  219. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/ferrocene_pbc_24a.xyz +0 -0
  220. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2.charges +0 -0
  221. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2.multipoles +0 -0
  222. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2.xyz +0 -0
  223. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2_pbc_12a.xyz +0 -0
  224. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2_slab.xyz +0 -0
  225. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/spin_params_template.txt +0 -0
  226. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/water_dimer_distorted.xyz +0 -0
  227. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/water_dimer_pbc_16a.xyz +0 -0
  228. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/cutoff.rs +0 -0
  229. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/element_coverage.rs +0 -0
  230. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/error.rs +0 -0
  231. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/integrals_second_derivatives.rs +0 -0
  232. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/lr_exchange.rs +0 -0
  233. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/math.rs +0 -0
  234. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/neighbor.rs +0 -0
  235. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/one_center_d.rs +0 -0
  236. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/pairlist.rs +0 -0
  237. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/pme.rs +0 -0
  238. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/range_separation.rs +0 -0
  239. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/spin_projection.rs +0 -0
  240. {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/sto.rs +0 -0
@@ -0,0 +1,2064 @@
1
+ # Changelog
2
+
3
+ All notable changes to gfn2-rs. Versions before 0.5.0 had no changelog; the
4
+ 0.4.x entries below are reconstructed from release notes that were scattered
5
+ through `docs/` and source comments.
6
+
7
+ ## [0.6.1] - released
8
+
9
+ ### Fixed (licensing) — the binaries now carry the notices they owe
10
+
11
+ - **The published binaries shipped third-party data with no licence text and no
12
+ attribution notice.** `_native` (the PyPI wheel) and the CLI executables
13
+ compile in LGPL-3.0 data (the xtb GFN2 parameter file, the tblite spGFN2 spin
14
+ constants, the dftd4 reference tables) and Apache-2.0 data (multicharge's
15
+ EEQ-BC parameters, mctc-lib's element data), and statically link ~50 MIT /
16
+ Apache-2.0 Rust crates. The wheel's `dist-info/licenses/` held only this
17
+ project's own GPL `LICENSE`; `THIRD_PARTY_NOTICES.md` did not travel at all;
18
+ the embedded-data modules *paraphrased* the upstream headers instead of
19
+ retaining them; and no crate carried any notice anywhere. That is a gap
20
+ against Apache-2.0 § 4(a) (include the licence) and § 4(b) (retain the
21
+ attribution notices), LGPL-3.0 § 4 (include the licence), and MIT's
22
+ requirement to include the notice "in all copies or substantial portions" —
23
+ a linked binary being a substantial portion.
24
+
25
+ What now ships, in **every** channel (wheel `dist-info/licenses/`, crates.io
26
+ package, source archive) and **embedded in the binaries themselves**:
27
+
28
+ - `LICENSES/` — the verbatim texts of GPL-3.0, LGPL-3.0, Apache-2.0,
29
+ LLVM-exception, MIT, BSD-3-Clause, MPL-2.0, Zlib, Unicode-3.0 and
30
+ CC-BY-4.0.
31
+ - `NOTICE` — the upstream licence header of each embedded artefact,
32
+ **quoted verbatim**, with its pinned commit and method citation.
33
+ - `THIRD_PARTY_LICENSES_RUST.md` — 630 kB, generated by the new
34
+ `scripts/collect_rust_licenses.py` from `cargo metadata` and the crate
35
+ sources: every `LICENSE*` / `COPYING*` / `NOTICE*` / `AUTHORS*` file of all
36
+ 83 resolved crates, verbatim, including `faer`'s `COPYING.EIGEN.MPL2`,
37
+ `COPYING.LAPACK.BSD` and `COPYING.SUITE_SPARSE.{AMD,COLAMD}.BSD` for its
38
+ Eigen-, LAPACK- and SuiteSparse-derived kernels. The file records the
39
+ SHA-256 of the `Cargo.lock` it was generated from.
40
+ - `pyproject.toml`'s PEP 639 `license-files` now lists all of the above.
41
+
42
+ New `--licenses` flag on `gfn2_rs`, `nn_train` and `nn_eval` (and
43
+ `gfn2_rs.third_party_licenses()` in Python) prints the lot, so an executable
44
+ or `.pyd` copied out of its package still carries its notices.
45
+
46
+ `tests/attribution.rs` (new) pins it: every source file has an SPDX header,
47
+ every `include_str!`-ed file is named in `NOTICE` and
48
+ `THIRD_PARTY_NOTICES.md`, every referenced SPDX id has its text in
49
+ `LICENSES/`, the embedded-data modules quote their upstream header, and the
50
+ generated crate document matches the current `Cargo.lock` hash — so a
51
+ dependency bump without regeneration fails the build.
52
+ `tests/test_wheel_licenses.py` (new) checks the installed wheel's
53
+ `dist-info/licenses/`.
54
+
55
+ ### Fixed
56
+
57
+ - **`NnThirdDerivativeMode::Chain`'s rejection named blockers that had already
58
+ been removed.** The refusal message is the only account of the remaining work
59
+ a caller ever sees, and it was written when groups (5), (6) and (7) were
60
+ unbuilt. It kept reporting group (7) as waiting on "a third-order encoder
61
+ pass: `nn::tape` stops at forward-over-reverse" after `nn::tape` had grown
62
+ `forward_tangent2` / `backward_with_two_tangents`, and groups (5) and (6) as
63
+ waiting on raw `M` and `K` tensors that `correction_blocks_with_tangents`
64
+ supplies. The refusal itself was correct — the tensor is not assembled — but
65
+ it sent a reader toward finished work.
66
+
67
+ The table now carries a state per group (`BUILT, not summed` vs `MISSING`),
68
+ says group (2) is done for the repulsion family, and enumerates all six
69
+ chain groups. `chain_mode_refuses_and_names_what_is_missing` pins the
70
+ built/missing distinction and every group number, so the message cannot go
71
+ stale silently again. No numerical change: nothing about which groups are
72
+ summed has moved.
73
+
74
+ - **The default k-point mesh was Gamma-centred only for even `n`, and an odd
75
+ grid silently broke the crystal's symmetry.** `mp_coord` produced
76
+ `i/n - 1/2`. That is the same SET as `i/n` for even `n`, but for odd `n` it
77
+ is not: `n = 3` gave `{-1/2, -1/6, 1/6}`, which is not a subset of `(1/3)Z`.
78
+
79
+ A mesh whose coordinates are not all multiples of `1/n` is not closed under
80
+ the point group of a lattice whose symmetry operations MIX the primitive
81
+ reciprocal axes — face-centred cubic is the everyday case — and the symmetry
82
+ it loses is visible in the answers rather than hidden in a convention.
83
+ Diamond's triply degenerate Gamma optical mode came out **split**: at
84
+ `--kgrid 3 3 3` the three modes were 1182.29 / 1315.43 / 1315.43 cm^-1 and at
85
+ `5 5 5` they were 1268.43 / 1277.81 / 1277.81, a 1 + 2 splitting with no
86
+ physical cause. The default is now `i/n` folded into `[-1/2, 1/2)`, and the
87
+ same two grids give **1366.19 x 3** and **1282.00 x 3**; the whole sequence
88
+ (1595.35, 1366.19, 1300.37, 1282.00, 1276.93 for `n = 2..6`) is now monotone
89
+ in `n` where it used to zig-zag between the parities.
90
+
91
+ Even meshes are unaffected: the set is identical, and
92
+ `even_meshes_are_the_same_set_as_before_the_fix` pins that. `kpoint_shifted`
93
+ is untouched and remains the textbook Monkhorst-Pack offset
94
+ `(2r - n - 1)/(2n)`; for odd `n` it now coincides with the default, so the
95
+ odd-mesh half-step workaround that `dfpt`'s supercell-fold tests used to need
96
+ is retired.
97
+
98
+ The property, not the formula, is what is pinned:
99
+ `kpoints::mesh_tests::the_default_mesh_is_closed_under_the_fcc_point_group`
100
+ rotates every mesh point by two generators of the cubic group and requires it
101
+ to land back on the mesh modulo a reciprocal lattice vector, and
102
+ `the_old_default_was_not_closed_for_odd_meshes` keeps the record of what that
103
+ is protecting against by asserting the old formula's closure held for even
104
+ `n` only.
105
+
106
+ - **A wire's SCC was summing `1/r` with a spherical cutoff, and the result
107
+ jumped with the cutoff rather than converging.** Five dispatches sent a
108
+ one-axis cell to a truncated direct sum: `Gfn2PbcModel::isotropic_scc_energy`,
109
+ `gradients::scc_forces_virial`, and — the ones the SCC actually runs
110
+ through — the shell-resolved kernel, energy and potential in
111
+ `electronic/scc_common.rs` plus their analytic-gradient counterparts in
112
+ `native_gradient.rs`. All of them now take the same route a slab does.
113
+
114
+ Measured on a tilted HF chain (`a = 6.2` bohr), sweeping `--scc-cutoff`:
115
+ before, the energy moved by 9.0e-4 Eh and was **discontinuous** — 20/40/60/70
116
+ bohr converged smoothly while 30/50/80/100/130 jumped by up to 4e-3 Eh,
117
+ because a spherical cutoff keeps an asymmetric set of images of a sum whose
118
+ cancellation needs them in pairs. After, the same sweep is monotone with a
119
+ total spread of 1.4e-5 Eh, and what is left is the short-range
120
+ `gamma_KO - 1/r` residual, which along one axis converges absolutely.
121
+
122
+ The energy is also now **exactly** independent of the vacuum padding: 0.0 Eh
123
+ difference between 40 and 300 bohr of vacuum, where a 3D Ewald applied to a
124
+ wire drifts.
125
+
126
+ - **`--gradients` alone printed no gradient.** It did not imply `--electronic`,
127
+ so the run fell through to the fixed-charge path and reported a
128
+ repulsion-only energy with no gradient and no warning — the flag simply did
129
+ nothing. Every other output flag (`--orbitals`, `--stability`, `--molden`,
130
+ `--hessian`) already implied it. `--gradients` and `--stress` now do too.
131
+ Nothing that worked before changes: a caller who passed `--gradients
132
+ --electronic` is unaffected, and one who passed `--gradients` alone was
133
+ getting no gradient.
134
+
135
+ Found by the new `tests/cli_matrix.rs`, which runs each documented mode
136
+ combination end to end and asserts the output keys it promises rather than
137
+ only its exit status — "exit 0" is satisfied by a run that produced nothing.
138
+ Thirty-three combinations across molecular, periodic and refused cases.
139
+
140
+ - **The anisotropic-electrostatics slab correction double-counted the
141
+ charge-charge dipole layer, making every slab energy depend on the vacuum
142
+ thickness.** A 3D Ewald sum used on a 2D-periodic cell introduces a spurious
143
+ `2 pi M_n^2 / V` dipole-layer term, which a Yeh-Berkowitz correction removes.
144
+ The Coulomb path applies that correction with the CHARGE moment `M^q`, and the
145
+ AES path applied it with the FULL moment `M^q + M^mu` — but the AES Ewald sum
146
+ deliberately leaves charge-charge to the Coulomb path, so its own artefact is
147
+ only the cross and dipole-dipole parts. The extra `(M^q)^2` was a term no sum
148
+ had produced.
149
+
150
+ It is proportional to `1/V`, so on a slab it decays only as one over the
151
+ vacuum thickness. Measured on a water layer in a 12 x 12 bohr cell: the total
152
+ energy moved 1.4e-4 hartree when the vacuum was doubled from 60 to 120 bohr,
153
+ and halved with every further doubling instead of settling. After the fix the
154
+ same sweep from 60 to 480 bohr moves 3.0e-14 hartree. The Coulomb slab sum was
155
+ already exact (vacuum-independent to 3e-11 at fixed charges); AES was the
156
+ whole of the drift, confirmed by switching it off.
157
+
158
+ `aes::slab_normal_moments` now returns both moments and the correction uses
159
+ their difference, with the potentials, the gradient/stress and the CPHF
160
+ potential derivatives all following. Pinned by
161
+ `physics::slab_energy_is_independent_of_the_vacuum_thickness`, which asserts
162
+ flatness for the slab and prints how far the same layer treated as a crystal
163
+ is from flat (2.8e-4 hartree per doubling), so the reason for the separate 2D
164
+ path is on the record.
165
+
166
+ **Any slab or wire energy from an earlier version that included AES is
167
+ affected**, by an amount set by the vacuum thickness the input happened to
168
+ use.
169
+
170
+ - **`lattice_gradient_from_strain_derivative` contracted the wrong index of the
171
+ inverse cell, and was wrong for every non-orthorhombic cell.** For
172
+ `cell' = (I + eps) cell` with the atoms at fixed fractional coordinates, the
173
+ chain rule through `eps = dA A^-1` gives
174
+ `dE/da_{i,c} = sum_beta S[c][beta] (A^-1)[i][beta]` — the strain derivative
175
+ contracts with the ROW of `A^-1` belonging to the lattice vector being
176
+ differentiated. The code used `(A^-1)[beta][i]`, the transpose. `A^-1` is
177
+ symmetric exactly when the cell is orthorhombic, and every periodic fixture
178
+ in the test suite is orthorhombic, so nothing caught it; measured on a
179
+ triclinic cell the error is 4 % on `dE/da_x` of the first lattice vector.
180
+ The function is public and documented but had no in-tree callers, so no
181
+ shipped number moves.
182
+
183
+ Pinned by `native_gradient::tests::lattice_gradient_conversion_matches_a_model_energy_finite_difference`,
184
+ which differences a model energy of the cell alone in BOTH parametrisations
185
+ (strain and lattice vector) so neither side restates the formula, plus two
186
+ end-to-end tests against the real GFN2 energy on triclinic and orthorhombic
187
+ cells.
188
+
189
+ **Recorded while writing those tests:** the total energy is only
190
+ *piecewise* smooth in the cell. Image counts are `ceil(cutoff / plane_height)`,
191
+ so a cell whose plane height divides a cutoff exactly sits on a step of that
192
+ ceiling. On a `9, 10, 11` bohr cell (`30 / 10` is exactly 3) every derivative
193
+ touching the `b` axis carried a fixed ~1.5e-7 Eh jump, and its
194
+ finite-difference error therefore grew as `1/h`: 4.2e-4 at `h = 2e-4`,
195
+ 7.7e-4 at 1e-4, 1.5e-3 at 5e-5. This matters for cell relaxation, not just
196
+ for tests.
197
+
198
+ ### Added
199
+
200
+ - **The NN-xTB Hessian and Molden export reached Python and ASE
201
+ (`gfn2_rs.NnCalculator`, `GFN2RSCalculator.get_hessian` / `export_molden`).**
202
+
203
+ The Rust library has had a fully analytic molecular NN Hessian and an
204
+ overlaid Molden export since the CLI's `--nn-weights` guard was narrowed —
205
+ `--nn-weights --freq` and `--nn-weights --molden` both work. The Python
206
+ binding never exposed either, so the ASE calculator refused all four of
207
+ `get_tda` / `get_td_gradient` / `get_hessian` / `export_molden` with a
208
+ message that also mis-stated the reason ("The Rust API implements the NN-xTB
209
+ Hessian and TD gradients"; it implements the Hessian and refuses the TD
210
+ gradients).
211
+
212
+ `NnCalculator` holds a trained model open across calls and exposes
213
+ `hessian()` — returning `NnHessianResult` with the matrix in both unit
214
+ systems, the fallback flags and a `source` string — and `export_molden()`,
215
+ closed shell only. `GFN2RSCalculator.get_hessian` and `export_molden` route
216
+ through it when `nn_weights` is set; the reference SCC is tightened for the
217
+ Hessian exactly as the CLI tightens it. Keywords with no NN counterpart
218
+ (`hessian_mode="fd"`, `relaxed=False`, the `include_*` toggles, `cphf_*`,
219
+ `hessian_fd_step`) are refused by name rather than silently ignored.
220
+
221
+ `get_tda` and `get_td_gradient` still refuse, and the message now says why
222
+ correctly: the library refuses an excited-state request on an NN-overlaid
223
+ density too, so it is not a binding gap that a wrapper could close.
224
+
225
+ - **The PyPI wheel now carries a trained NN-xTB model, and names it
226
+ (`gfn2_rs.bundled_model_path()`, `gfn2_rs.BUNDLED_MODEL_NAME`).**
227
+
228
+ The trained weights are the one runtime input that is not
229
+ `include_str!`-compiled — `--nn-weights` and `nn_weights=` open a path — so
230
+ through 0.6.0 a `pip install` could not run the NN-xTB surface at all: no
231
+ model shipped in the wheel, and the `models/...` spelling in the docs only
232
+ resolves from a checkout or an unpacked archive. `bundled_model_path()`
233
+ returns an absolute path inside the installed package, and raises
234
+ `FileNotFoundError` naming where else to get the file if an installation was
235
+ built without its package data.
236
+
237
+ Mechanically the model exists twice: `models/` for the archive and a
238
+ byte-identical copy at `python/gfn2_rs/models/` that `maturin` puts inside
239
+ the wheel. Shipping both copies in the crate would have taken it to 16.4 MiB
240
+ compressed, past the 10 MiB crates.io accepts without an exemption, so the
241
+ crate and the sdist carry the package copy and the zip carries `models/`.
242
+ `tests/attribution.rs` hashes the two and fails if a release refreshes one
243
+ and forgets the other; `tests/test_bundled_model.py` checks the built wheel,
244
+ the installed distribution, and that the model loads and moves the energy off
245
+ plain GFN2. `--nn-weights` gained a load-failure message that says where a
246
+ model comes from in each channel. Reasoning and sizes in
247
+ `docs/pip_reachability.md`; the superseded model stays out of the wheel.
248
+
249
+ - **Analytic third energy derivatives on the NN-xTB PES, staged
250
+ (`src/nn/third_derivative.rs`, `src/nn/third_repulsion.rs`).** Molecular
251
+ only, matching `crate::third_derivatives`.
252
+
253
+ Because the network's parameters move with the geometry, `d^3E/dR^3` is not
254
+ the GFN2 tensor at shifted parameters: the chain rule splits it into seven
255
+ groups (see `docs/nn-xtb.md`). This release assembles group (1)
256
+ — the fixed-parameter tensor, reachable as
257
+ `NnThirdDerivativeMode::FrozenParameters` and through
258
+ `cubic_check --nn-weights` — plus groups (5), (6), (7), and group (2) for
259
+ the repulsion family. Groups (3) and (4) are FROZEN work, and no other
260
+ family implements group (2).
261
+
262
+ `NnThirdDerivativeMode::Chain`, the assembled tensor, is **REFUSED** with an
263
+ error naming every missing group. A partial sum is never returned.
264
+
265
+ Measured: the frozen-parameter mode captures ~91% of the true third
266
+ derivative on water with the shipped model (gap 9.2% against the central
267
+ difference of the exact analytic NN Hessian). Group (7) agrees with the FD
268
+ of the Hessian's curvature block at 2.3e-10; the repulsion family's group
269
+ (2) agrees with the FD of that family's Hessian block at 9.8e-9.
270
+
271
+ Underneath: `Tape::forward_tangent2` and `Tape::backward_with_two_tangents`
272
+ (third-order tape passes, 2.7e-11 against FD, and reproducing the existing
273
+ first- and second-order passes to 1e-12 so the Hessian path provably did not
274
+ move); `RadialBasis::values_and_three_derivatives`; a constant
275
+ third-derivative table for the real spherical harmonics;
276
+ `MaceEncoder::position_gradient_second_tangent` (9.0e-11 against FD of the
277
+ analytic HVP). `slot_space_to_cartesian_pair` makes the `K` contraction's
278
+ second factor a parameter while keeping the slot-space accumulation the
279
+ block is shaped around — pinned BIT-for-BIT against the previous square
280
+ form.
281
+
282
+ - **`nn_train --bound clamp|tanh`: a smooth alternative to the paper's hard
283
+ clamp.** `B(x) = SHIFT_LIMIT tanh(SHIFT_SCALE x / SHIFT_LIMIT)` is analytic,
284
+ so its first, second and third derivatives are exact everywhere instead of
285
+ exact only where the clamp is inactive. It agrees with the clamp to third
286
+ order in a small argument (0.003% at a tenth of the bound, 0.3% at half) and
287
+ saturates to the same limit.
288
+
289
+ Why it matters: at an ACTIVE clamp the true `dB/dx` is `scale` on one side
290
+ and `0` on the other, so the PES has a kink — the Hessian carries a delta
291
+ and the third derivative does not exist. No backward convention repairs
292
+ that. Away from the bound the straight-through convention IS the exact
293
+ derivative, and `saturation_fraction` (a MAXIMUM over heads, not a fraction
294
+ of them) below 1.0 means no clamp is active anywhere.
295
+
296
+ The choice travels in the `.gfn2nn` header as `shift_bound`; its absence
297
+ means the clamp, so every previously written model reads back unchanged and
298
+ a clamp model's header is byte-identical to before. Weights do not transfer
299
+ between the two bounds once any head approaches the limit.
300
+
301
+ - `--cphf-tol` / `--cphf-max-iter` on `gfn2_rs` and `hessian_check`, and in
302
+ the Python/ASE API. The relaxed Hessian is FIRST ORDER in the CPHF
303
+ solver's residual, so tightening these is how one tells an iterative
304
+ stopping point from a defect — which is how the k-point Hessian defects
305
+ below were separated from convergence noise.
306
+
307
+ - `gfn2_rs --freq` and `--molden` now run on the NN-xTB path, and
308
+ `nn_eval --per-reaction FILE` writes one TSV row per scored reaction
309
+ (name, family, species count, reference, per-method errors, `ref_method`,
310
+ `provenance`) so any grouping can be made without a second evaluation.
311
+
312
+ - `hessian_mode_used` is now printed by `gfn2_rs --freq` and `hessian_check`,
313
+ with a `hessian_mode_note` when what ran differs from what was asked for.
314
+ Until now no surface printed it: `--freq` emitted a hard-coded
315
+ `mode native-hessian` and a caller could not tell an analytic Hessian from a
316
+ differenced one.
317
+
318
+ - MACE, ACE and DimeNet reference citations in `src/nn/mace.rs`,
319
+ `src/nn/radial.rs` and `src/nn/cg.rs`. The architecture is not this
320
+ project's; only the reimplementation is, and the source now says whose it
321
+ is.
322
+
323
+ - **Exact Coulomb summation for a cell periodic along ONE axis
324
+ (`src/wire_sum.rs`).** A wire no longer falls back to a truncated direct sum.
325
+
326
+ There is no 1D Ewald here because there cannot usefully be one: split `1/r`
327
+ the Ewald way with a single periodic direction and the reciprocal term is an
328
+ *incomplete* Bessel function, whose series in the transverse distance
329
+ alternates with terms of size `(k rho/2)^{2p}/p!` and loses every digit
330
+ exactly where a wire needs them. So this does not split. It evaluates the
331
+ lattice sum in closed form, by two expansions that overlap:
332
+
333
+ * **near** (`r <= 0.7 L`): the Legendre expansion of each image, summed over
334
+ the image index, which turns into the Riemann zeta function 窶・ `psi = 1/r + 2 gamma/L + (2/L) sum_{k even} zeta(k+1) P_k(z/r) (r/L)^k`;
335
+ * **far**: Poisson summation along the wire 窶・ `psi = (2/L) ln(2L/rho) + (4/L) sum_m K_0(2 pi m rho/L) cos(2 pi m z/L)`.
336
+
337
+ Every individual term of a 1D Coulomb sum diverges logarithmically; what is
338
+ subtracted is one constant, the same for every pair, so it cancels exactly
339
+ for a neutral cell. A charged wire has no finite Coulomb energy at all, and
340
+ **is refused by name** 窶・not even with `--charged-pbc background`, because
341
+ the other two axes are vacuum and a background confined to the cell does not
342
+ remove the divergence.
343
+
344
+ Each piece is checked against something that did not build it: `K_0`/`K_1`
345
+ against the Wronskian `I_0 K_1 + I_1 K_0 = 1/x` (worst residual 1e-14 over
346
+ `x` in [0.1, 60]), zeta against its closed forms, the near expansion against
347
+ `psi(0, L/2) = (2/L)(gamma + 2 ln 2)` derived from the digamma function, the
348
+ two expansions against each other across their whole overlap, the whole
349
+ function against a brute-force sum over a million images, and the assembled
350
+ energy against the **exact 1D Madelung constant `2 ln 2`** (agreement to
351
+ 1e-13 relative). Forces and the full 3x3 strain derivative match finite
352
+ differences in every component; the period derivative comes from Euler's
353
+ homogeneity relation rather than a third series.
354
+
355
+ - **Cell relaxation: `--opt-cell fixed|volume|full`.** The L-BFGS optimizer now
356
+ steps in six strain parameters alongside the atomic coordinates, so a
357
+ periodic structure can relax its cell. `full` frees shape and volume,
358
+ `volume` frees the shape and holds the volume *exactly* (not to first order
359
+ in the strain: the deformation is rescaled by `det(I + eps)^(-1/3)`, measured
360
+ to preserve the volume to 1.5e-16 relative), `fixed` is the default and the
361
+ behaviour every earlier version had.
362
+
363
+ Only strains between periodic axes are free, so a slab relaxes in its plane
364
+ and never along the vacuum, and `--opt-cell volume` on a slab is refused by
365
+ name — a slab's cell volume is whatever vacuum thickness the input chose, so
366
+ holding it fixed constrains nothing physical.
367
+
368
+ The strain gradient is `V sigma F^-T`, which is the place a transposed
369
+ contraction changes nothing on a cubic cell and everything on a triclinic
370
+ one, so it is verified against a central finite difference of the energy the
371
+ same code produces, component by component over all `3N + 6` parameters, on a
372
+ triclinic cell at non-zero strain: 8.6e-7 for `full`, 4.0e-7 for `volume`.
373
+ `physics::cell_relaxation_finds_the_same_wire_lattice_constant_from_either_side`
374
+ relaxes a hydrogen wire from 4.2 and from 2.6 bohr and requires the same
375
+ answer (3.19370 against 3.19365, and the same energy to 8e-9 Eh).
376
+
377
+ Convergence needs BOTH the force and the free stress components below their
378
+ tolerances (`--opt-stress-tol`, default 1e-6 Eh/bohr^3); the per-iteration
379
+ line and the trace TSV carry `stress_max` and `volume_bohr3`.
380
+
381
+ The fixed-cell path is unchanged **bit for bit** —
382
+ `physics::fixed_cell_optimization_is_unchanged_by_the_cell_parameter_space`
383
+ compares the two trajectories by `f64::to_bits`.
384
+
385
+ - **Orbital energies and the frontier: `--orbitals`, `--orbital-coefficients`.**
386
+ The SCC always produced orbital energies and occupations; nothing reported
387
+ them. `ElectronicResult::frontier_orbitals()` now locates the HOMO, the LUMO
388
+ and the gap per spin channel, and the CLI, the Python bindings
389
+ (`result.homo_lumo_gap_ev`, `result.orbital_energies_hartree`, ...) and the
390
+ ASE calculator (`get_orbital_energies()`, `get_homo_lumo()`) expose them.
391
+
392
+ Three things the definition has to get right, each pinned:
393
+ - occupations are fractional at any non-zero electronic temperature, so the
394
+ split is a threshold at half the channel capacity, and
395
+ `integral_occupations` says whether that was a formality or a judgement
396
+ call;
397
+ - a doublet's singly occupied orbital sits at exactly half capacity in the
398
+ spin-summed channel and is the HOMO, not the LUMO — with a tolerance,
399
+ because a finite-temperature fill lands it a few parts in 1e9 short;
400
+ - on a k-point run the gap is the FUNDAMENTAL one, taken across the whole
401
+ Brillouin zone, and `gap_is_indirect` says when the two ends sit at
402
+ different k-points.
403
+
404
+ Refused on the divide-and-conquer path, whose pooled subsystem eigenvalues
405
+ are not bands of one Hamiltonian.
406
+
407
+ - **SCC convergence diagnosis: `scc_verdict`, `scc_class`, `--scc-trace`,
408
+ `--scc-preset`, `--kerker-beta`.** `ElectronicResult::scc_residual_history`
409
+ records the residual of every iteration on every driver, and
410
+ `ResidualMonitor` reads the ratios back as one of three verdicts —
411
+ *converging*, *sloshing* (over-correcting) or *stiff* (converging at a rate
412
+ that will not arrive). The last two matter because their remedies are
413
+ opposites: damping a stiff iteration makes it creep more slowly.
414
+
415
+ On a stiff verdict the CLI takes the remedy the ladder cannot express before
416
+ walking the ladder: `anneal_electronic_temperature` converges at four times
417
+ the requested electronic temperature and halves its way back down, ending at
418
+ exactly the requested value — measured against a direct solve, 3.6e-15 Eh at
419
+ 300 K.
420
+
421
+ `classify_from_frontier` labels a solution insulator or metallic (gap below
422
+ 0.02 Eh, or fractional occupations), `--scc-preset auto|insulator|metal`
423
+ selects the matching settings, and `auto` resolves itself by measuring the
424
+ first solve rather than guessing before one exists.
425
+
426
+ `--kerker-beta` implements the charge-space analogue of plane-wave Kerker
427
+ mixing, damping each eigenmode of the shell Coulomb kernel by
428
+ `1/(1 + beta lambda)` (verified eigenmode by eigenmode). It is **off by
429
+ default and measured not to help**: on the metallic fixtures it ranges from
430
+ neutral to harmful, and on the one hard case that converged without it, it
431
+ stops it converging. `docs/measurements.md` records why — the
432
+ instability is in the self-consistent MULTIPOLE response, not the charge
433
+ response, so a charge-space filter cannot reach it.
434
+
435
+ - **`docs/measurements.md`**, the periodic-SCC census: every shipped
436
+ periodic example at three electronic temperatures and both accelerators, plus
437
+ a half-filled hydrogen chain and a polar HF chain. The finding, stated as a
438
+ 2x2 rather than a guess: a metal fails only with self-consistent multipoles
439
+ AND zero electronic temperature, and any symmetry breaking at all — 1e-8 bohr
440
+ is enough, about what an angstrom-to-bohr conversion costs — is enough to
441
+ trigger it.
442
+
443
+ - **`docs/pip_reachability.md`**, an audit of what survives each distribution
444
+ channel. Two things are reached by path rather than compiled in (the NN
445
+ trainer's default dataset and the Ln-xTB parameter generator), and both now
446
+ name the source repository in their error and their `--help`.
447
+
448
+ The wheel was built and every entry listed rather than inferred from the
449
+ configuration: all ten `LICENSES/*.txt`, `LICENSE`, `NOTICE`,
450
+ `THIRD_PARTY_NOTICES.md` and `THIRD_PARTY_LICENSES_RUST.md` are in
451
+ `dist-info/licenses/` (784 kB of a 3.84 MB wheel), and the training records,
452
+ the root `nnxtb_model.gfn2nn`, `.pdb` symbols and `__pycache__` are not.
453
+ `tests/test_wheel_licenses.py` now derives its required list from the
454
+ repository's own `LICENSES/` directory instead of a hand-written one, checks
455
+ a built `.whl` as well as the installed distribution, and requires each
456
+ licence entry to be non-empty — an empty file satisfies a file-list check and
457
+ no licence.
458
+
459
+ `docs/` is deliberately **not** in the wheel: 421 kB with no licence text in
460
+ it, and a wheel is a runtime artefact. The three runtime messages that named
461
+ a `docs/` file now say it lives in the source repository, and a test pins the
462
+ decision so including them later has to be deliberate.
463
+
464
+ ### Performance
465
+
466
+ Measured on a 480-atom water cluster (`--electronic --gradients`) profiled with
467
+ `GFN2_PROFILE=1`, against the same binary before each change.
468
+
469
+ - **The DIIS error vector cost twice the matrix multiplications it needed.**
470
+ `F P S - S P F` was built from two triple products. `F`, `P` and `S` are all
471
+ symmetric, so `(F P S)^T = S P F`: the second product was recomputing the
472
+ transpose of the first. One triple product now serves both, in the new
473
+ `scc_accel::commutator_fps_minus_spf`, which the orthonormal, AO and
474
+ long-range-corrected paths all share.
475
+
476
+ `electronic.scf.fock_accel` fell from 4417 ms to 3134 ms (**-29%**, six
477
+ `O(n^3)` products down to four) and the whole run from 22.0 s to 19.5 s
478
+ (**-11%**). The error is now *exactly* antisymmetric, where the two-product
479
+ form was antisymmetric only to rounding.
480
+
481
+ - **The 2D neutral-difference Ewald matrix ran a full slab Ewald once per
482
+ column.** `nat` system clones, `nat` neighbour-list builds and `nat`
483
+ reciprocal sums, where one of each suffices: the 3D part is the geometry
484
+ matrix the 3D arm already uses, and the Yeh–Berkowitz correction is **rank
485
+ one** (`(4 pi/V) z_i z_j`) and needs no Ewald at all. That removes a factor of
486
+ `nat` from the real-space and cloning cost of every slab SCC setup.
487
+ `the_slab_neutral_difference_matrix_matches_the_column_by_column_form` pins
488
+ the new build against the definition it replaced, for every reference atom, on
489
+ a skewed cell.
490
+
491
+ - **The 1D neutral-difference matrix is built the same way**, in one pass over
492
+ the pairs rather than one potential evaluation per column.
493
+
494
+ Not changed, and recorded because they are what a profile points at next on this
495
+ system: the D4 three-body term is 34% of the run and is a genuine `O(N^3)`
496
+ triple sum for any cluster smaller than its 40 bohr cutoff (already the dftd4
497
+ value); the dense diagonalization is 23% and inherently cubic; and the DIIS
498
+ history holds three matrices per entry where only the Fock matrices are needed
499
+ for the extrapolation — at 12 entries and 960 basis functions that is 266 MB of
500
+ the run's 475 MB peak, and the densities and errors are consumed only through
501
+ scalars that could be accumulated when an entry is pushed.
502
+
503
+ ### Known limitations
504
+
505
+ - **This bundled NN-xTB model is still worse than plain GFN2 on non-covalent
506
+ interactions and conformer energies.** A limitation of the trained model, not
507
+ of the method.
508
+
509
+ The v0.6.1 retrain uses `--energy-normalization wtmad2`, which exists to fix
510
+ one cause: the v0.6.0 model trained on an unweighted squared kcal/mol loss, so
511
+ high-energy subsets took the whole gradient budget. It **reduces the effect
512
+ without removing it** — the aggregate and the large-error families improve
513
+ against the v0.6.0 model, while `s22`, `s66`, `aconf` and `sconf` stay above
514
+ plain GFN2's own error.
515
+
516
+ The other cause is what this schema lets the network reach on dispersion. The
517
+ learnable D4 surface is four **element-independent** globals (`a1`, `a2`,
518
+ `s8`, `s9`), all of which *were* trained here; the C6 coefficients come from
519
+ the dftd4 reference tables and none of those is learnable, so the model can
520
+ only rescale dispersion uniformly. The one element-resolved handle on the
521
+ neighbouring physics, `atom.dpol` (the AES dipole polarizability), was
522
+ **dropped from this run** because it had saturated at ~0.99 of its clamp in
523
+ the v0.6.0 model. Restoring it with a wider clamp, and giving D4 an
524
+ element-resolved head, is where the next run goes.
525
+
526
+ **How much that costs depends on what you are computing**, and the guidance
527
+ is calibrated against each family's own energy scale rather than against the
528
+ MAE alone (v0.6.0 model, default fallback; the `median |ref|` column is a
529
+ property of the dataset):
530
+
531
+ | family | median \|ref\| | GFN2 | model | model, as % of ref |
532
+ | --- | ---: | ---: | ---: | ---: |
533
+ | W4-11 atomization | 280.8 | 120.73 | 37.06 | **13%** |
534
+ | MOR41 | 34.2 | 14.07 | 7.60 | **22%** |
535
+ | S22 | 4.54 | 0.77 | 1.26 | 28% |
536
+ | S66 | 4.19 | 0.73 | 1.41 | 34% |
537
+ | SCONF | 5.54 | 1.64 | 2.45 | 44% |
538
+ | ACONF | **1.30** | 0.19 | 1.26 | **97%** |
539
+
540
+ So: use it for atomization and transition-metal reaction energies; on S22 /
541
+ S66-scale interaction energies it is usable at roughly double the relative
542
+ error of plain GFN2; and **do not use it for conformer energy differences of
543
+ order 1 kcal/mol** (ACONF and anything like it), where its error is 97% of the
544
+ median energy difference — the error is the signal. Only that last case earns
545
+ a flat prohibition, and it earns it because of the scale of the question, not
546
+ because the MAE is large.
547
+
548
+ **Gradients are a separate question and do not follow the energies.** On the
549
+ 16 `zero_force` structures the model's residual force at the reference
550
+ geometry has a median of 1.00e-2 Eh/bohr against plain GFN2's 5.62e-3 (RMS
551
+ 1.03e-2 against 9.06e-3), and it is the better of the two on 6 of 16. The
552
+ direction matches the energies — it improves what GFN2 handles worst and
553
+ worsens what it handles best — with more scatter. With `n = 16`, all small
554
+ closed-shell first- and second-row molecules, that is not a verdict on forces
555
+ in general; against reference forces on the 217-structure held-out set the
556
+ model's force RMS does improve through training. For force-driven work
557
+ (optimization, MD, vibrations) pick on the case rather than assuming the
558
+ energy improvement carries over. Not a defect in the derivative code: the NN
559
+ gradient is finite-difference verified molecular and periodic, closed- and
560
+ open-shell, smeared and not.
561
+
562
+ Written into `models/README.md` and `docs/whats-new-0.6.1.md` as a
563
+ limitation rather than withheld or described as a measurement caveat. The
564
+ NN-xTB path remains experimental and opt-in.
565
+
566
+ - **A metallic cell at zero electronic temperature does not converge** on a
567
+ k-mesh. Measured, diagnosed as the self-consistent multipole response rather
568
+ than charge sloshing, and documented in `docs/measurements.md`. The
569
+ remedy is the finite electronic temperature the physics wants anyway, which
570
+ the shipped 300 K default provides.
571
+
572
+
573
+ - **No viewer has rendered a Molden file from this code.** The AO basis and the
574
+ spherical-d convention are verified numerically (the overlap round trip, and
575
+ the five d functions against the standard real solid harmonics to 1e-12);
576
+ nobody has opened one in Multiwfn or Avogadro.
577
+
578
+ - **DFPT supports metals.** A Fermi-smeared (fractionally occupied) reference
579
+ at `q != 0` was refused by name — *"The Mermin (fractional) response at
580
+ q != 0 is not implemented"* — and the module additionally overwrote whatever
581
+ occupations the SCF produced with a `T = 0` filling by band index, behind a
582
+ hard `1e-6` Eh frontier-gap gate. Now:
583
+
584
+ - both point sets are filled from the Fermi-Dirac distribution at the SCF's
585
+ **own** chemical potential (new
586
+ `occupations::occupations_at_fixed_fermi_level`). `k + q` is off the mesh,
587
+ and re-bisecting `mu` from its own spectrum would put the two halves of
588
+ every band pair in different equilibria;
589
+ - the band-pair difference quotients take their analytic `df/deps` limits at
590
+ a degeneracy (reusing the Mermin CPHF's own
591
+ `weight_difference_quotient`), which is what makes a gapless frontier well
592
+ posed: `e_r - e_p -> 0` is accompanied by `f_r - f_p -> 0`, because both
593
+ are the same smooth function of the energy;
594
+ - the frontier-gap gate applies to `T = 0` references only, and is reported
595
+ rather than enforced for smeared ones;
596
+ - integer-occupation references keep the exact `T = 0` algebra, unchanged, so
597
+ no gapped result moves.
598
+
599
+ **No chemical-potential shift belongs at `q != 0`, and that is exact rather
600
+ than an approximation:** a displacement wave of wavevector `q != 0` has no
601
+ `(k, k)` matrix element, so the first-order eigenvalue shift vanishes, and
602
+ with it the occupation response and the particle-number condition that would
603
+ fix `mu^x`. The Fermi-surface physics enters entirely through the `df/deps`
604
+ limit. At `q = 0` the force constants *are* the relaxed Hessian, which routes
605
+ smeared references to the crate's Mermin CPHF on its own.
606
+
607
+ Validated by `q_tests::smeared_reference_matches_the_supercell_fold` against
608
+ the exact `2 x 1 x 1` supercell fold — an independent route, since the
609
+ supercell's relaxed Hessian goes through the Mermin CPHF and never through
610
+ the `q`-point solver. On a reference whose worst occupation sits 0.518 away
611
+ from 0 or 2, the two agree to **3.7e-8 on a scale of 1.6e-1** (2.3e-7
612
+ relative).
613
+
614
+ - **`src/vibrations.rs`: one vibrational analysis, shared by `--freq`, the
615
+ NN-xTB frequency loss and the DFPT Gamma path.** The mass-weighted Hessian is
616
+ now diagonalised INSIDE an orthonormal basis of the rigid-body subspace's
617
+ orthogonal complement, so exactly `3N - n` internal modes come out and there
618
+ is no selection step at all.
619
+
620
+ This removes two defects that had drifted apart in two copies of the same
621
+ algorithm. `nn/freq_modes.rs` projected with `P = I - sum q q^T`, diagonalised
622
+ the full `3N x 3N` matrix and then **discarded the `n` eigenpairs of smallest
623
+ `|lambda|`** — i.e. it assumed the smallest-magnitude modes were the
624
+ projected ones, which fails for a flat internal mode or a mis-decided rank.
625
+ The CLI's copy did **no selection at all** and printed all `3N` values, so the
626
+ ~6 projected residues were listed as vibrations; on the non-stationary water
627
+ of `tests/physics.rs` that meant reporting `imaginary_modes 1` for a
628
+ −150.66 cm⁻¹ value that is a *rotation*, not a saddle direction.
629
+
630
+ Which rigid motions are zero modes is now decided by periodic
631
+ dimensionality: 3 translations always; rotations only about an axis parallel
632
+ to every periodic lattice vector. A molecule keeps 3 (2 linear, 0 for one
633
+ atom), a **1D wire keeps one** (`3N - 4`, or `3N - 3` when every atom lies on
634
+ the wire axis), and a **2D slab keeps none** (`3N - 3`, same as a crystal) —
635
+ rotating the home cell about a slab normal does not rotate its images. The
636
+ linearity decision uses the same collinearity predicate as the dataset
637
+ loader, so the trainer's mode count agrees with its reference count by
638
+ construction. The wavenumber constant `5140.4848`, previously written out in
639
+ three places, has one definition, checked against CODATA.
640
+
641
+ - **A second oracle: 216 single points against tblite 0.6.0 and xtb**, with
642
+ non-equilibrium geometries, in `tests/oracle_tblite.rs`. 54 molecules from H
643
+ to Hf, each at its constructed geometry (textbook bond lengths, already off
644
+ any GFN2 minimum) and at three further geometries displaced by 0.06, 0.13 and
645
+ 0.24 Angstrom RMS. The geometries are generated by
646
+ `scripts/make_tblite_oracle.py` from internal coordinates written in that
647
+ script, so the set carries no benchmark-database provenance, and they are
648
+ stored **with** the energies: a reference energy without the structure it
649
+ belongs to pins nothing.
650
+
651
+ | comparison | n | median | worst |
652
+ | --- | ---: | ---: | ---: |
653
+ | tblite vs xtb (the oracle's own spread) | 216 | 2.5e-9 | 2.0e-8 |
654
+ | this crate vs tblite, excluding titanium halides | 207 | 3.9e-9 | 1.7e-6 |
655
+
656
+ **What it found** (details and an all-element sweep in
657
+ `docs/measurements.md`): nine titanium-halide references are off by 5 to 24
658
+ Eh, and it is not the Hamiltonian. With `--scf-accel charge` this crate
659
+ reproduces tblite on all nine to 5.3e-7 Eh; with the DEFAULT Fock-space
660
+ ADIIS→CDIIS accelerator the SCC converges — reporting `scf_converged true` —
661
+ onto a different self-consistent solution with a titanium Mulliken charge of
662
+ −6.44 on an atom that has four valence electrons. GFN2's third-order term
663
+ goes as `Γ³q³` and so is unbounded below for `q < 0`; titanium's
664
+ `GAM3 = 1.767` is the deepest such well in its neighbourhood (Sc 0.50,
665
+ V 0.90, Zr 0.70), which is why titanium is the element that falls in. The
666
+ spurious solution is degenerate at the frontier: its occupations come out
667
+ fractional at 300 K, so `--tda` refuses it outright, while the physical
668
+ solution has a lowest TDA excitation of +0.101 Eh. Both halves are pinned by
669
+ the test, so neither a regression nor a silent fix can pass unnoticed.
670
+
671
+ - **Phonon and normal-mode eigenvectors.** `dfpt::modes_from_dynamical_matrix`
672
+ and `dfpt::phonon_modes_dfpt` return the mass-weighted eigenvectors next to
673
+ the wavenumbers, complex away from Gamma (the realification's upper half is
674
+ the real part and its lower half the imaginary one), with
675
+ `PhononModes::cartesian_displacements` for the `M^{-1/2}`-scaled pattern.
676
+ `frequencies_from_dynamical_matrix` now delegates to it and is bit-identical.
677
+ New CLI flag `--modes` prints, per mode, the unit-norm Cartesian displacement
678
+ pattern; with `--qpoint` it prints both parts, because the phase between
679
+ neighbouring cells is physical. Pinned by `D z = lambda z` and Hermitian
680
+ orthonormality at Gamma and at `q = 1/2`.
681
+
682
+ - `--qpoint QX QY QZ` on the CLI: phonon frequencies at one fractional
683
+ wavevector through the DFPT module, alongside the Gamma analysis.
684
+ - `dfpt::frequencies_from_force_constants` with `AcousticProjection`, and
685
+ `dfpt::enforce_acoustic_sum_rule`. The latter delivers exact row sums and
686
+ **reports** the asymmetry it cannot remove rather than iterating towards a
687
+ fixed point that does not exist: the on-site correction is symmetric only
688
+ when the row-sum block is, and neither re-symmetrising nor symmetrising the
689
+ correction converges.
690
+
691
+ - **SCC convergence: a stability test, a ladder that fixes what it rejects, and
692
+ a new default starting point.** The tblite oracle above showed the SCC
693
+ converging, and reporting success, onto solutions several Hartree from the
694
+ answer. Three changes, all measured:
695
+
696
+ - **`ElectronicOptions::initial_guess` defaults to `InitialGuess::Eeq`.**
697
+ Both reference programs start from a superposition of atomic densities;
698
+ this crate started from `q = 0`, which is the wrong basin for a polar
699
+ reference and is when a `q^3` term unbounded below can be overshot into.
700
+ `InitialGuess::Zero` reproduces the old behaviour. Measured: converged
701
+ energies do not move (water 2.7e-9 from tblite, benzene 1.7e-6, methanol
702
+ 2.6e-9, unchanged), and on its own this does NOT fix titanium.
703
+ - **`electronic::scc_charge_curvature` and a CLI ladder that uses it.** The
704
+ on-site curvature `eta_s + 2 gamma3_s q_s` is positive at a minimum in the
705
+ charges and negative at a maximum; it costs one multiply per shell. It
706
+ separates the two titanium tetrachloride solutions cleanly (-0.472 against
707
+ +0.308) and stays between +0.25 and +0.43 on healthy molecules. The CLI now
708
+ tests every converged answer with it, and when the answer fails, re-solves
709
+ the same equations on progressively more damped rungs
710
+ (`electronic::apply_scc_rung`, shared with the NN trainer), from both the
711
+ EEQ and the zero guess, and finally at charge tolerances relaxed to 1e-7
712
+ and 1e-6. The rung, the guess and any relaxed tolerance are reported;
713
+ `--no-scc-ladder` turns it off. Measured against tblite: TiCl4 4.98 →
714
+ 5.3e-7 Eh, WH2 18.7 → 1.3e-8, CrH2 2.6e-2 → 5.4e-8, IrH 2.9e-1 → 6.1e-9,
715
+ AsF 2.8e-2 → 6.8e-10.
716
+ - **`--scc-search`** walks the whole ladder even when the first answer passes
717
+ and keeps the LOWEST acceptable solution, for references with more than one
718
+ stable SCC solution: TcH 5.9e-2 → 9.6e-9, GeH2 9.3e-3 → 2.5e-8. On two
719
+ further probes it confirms this crate is already in the DEEPER minimum and
720
+ tblite is not.
721
+
722
+ - **`--stability` and `src/stability.rs`: stability analysis of a converged
723
+ solution.** Three questions, cheapest first: is it a minimum in the charges
724
+ (the curvature above), is the frontier gap open (fractional occupations at
725
+ 300 K mean it collapsed), and is it a minimum in the orbital rotations. The
726
+ last is the DFT/HF analysis proper, the lowest eigenvalue of the electronic
727
+ Hessian, read off the Tamm-Dancoff spectrum the crate already computes: the
728
+ singlet channel for internal stability, the triplet channel for external
729
+ (restricted-to-unrestricted) stability. A channel that cannot run reports
730
+ why rather than failing, because "this reference has no frontier gap" is an
731
+ answer.
732
+
733
+ - **Open-shell coverage against tblite, at both levels.** Occupation-only
734
+ (`-u N --no-spinpol` against `--spin N`) and spin-polarized (`-u N` against
735
+ `--spin N --spin-polarized`), 344 odd-electron probes each. Split by whether
736
+ this crate's SCC converged, the occupation-only path agrees to a median
737
+ 6.9e-9 and the spin-polarized path to 3.5e-9. The finding is that the
738
+ spin-polarized SCC misses its charge tolerance on 218 of 344 probes, against
739
+ 28 of 344 for the occupation-only path: a convergence gap, not a physics
740
+ one. Recorded in `docs/measurements.md`.
741
+
742
+ ### Changed (BREAKING)
743
+
744
+ - **`Cargo.toml`'s package list is now an `include` WHITELIST, not an
745
+ `exclude` blacklist — and the blacklist was leaking.** Measured with
746
+ `cargo package --list` before the change: the crates.io package carried a
747
+ locally built `_native.cp314-win_amd64.pyd` (11.8 MB — one Python ABI and
748
+ one OS, inside a *source* package), its `.pdb`, three `__pycache__` entries,
749
+ the root `nnxtb_model.gfn2nn` and seven optimizer scratch files. **21.3 MB**
750
+ of it. There is no git repository here, so `cargo package` has no
751
+ `.gitignore` to consult and an exclude list has to anticipate every artefact
752
+ the working tree can grow; this one had not.
753
+
754
+ Inverting the list dropped exactly those 15 files and nothing else, verified
755
+ by diffing `cargo package --list` across the change. `tests/` now ships in
756
+ the crate as well, so the suite (including `attribution.rs`, whose inputs
757
+ are all whitelisted) runs from an unpacked published crate.
758
+ `scripts/make_release_zip.py` is a whitelist for the same reason, and its
759
+ archive is unchanged file-for-file. `pyproject.toml` keeps only the two
760
+ wheel-payload entries; the sdist is governed by the Cargo whitelist, checked
761
+ by building one.
762
+
763
+ - **`nn_train --dataset` is REQUIRED and has no default.** Training data is
764
+ supplied by the caller. A default would name a path most callers do not
765
+ have, and would let a run silently pick up whatever happened to sit there.
766
+ The refusal names the flag and points at `docs/nn-xtb.md`, which now
767
+ carries the `.gfn2ds` format and a worked training procedure. The four
768
+ corpus tests in `src/nn/dataset.rs` take their directory from
769
+ `GFN2_NN_DATASET` and print `SKIPPED` when it is unset.
770
+
771
+ - **`PeriodicDimensionality::OneD` carries its periodic axis**, the way `TwoD`
772
+ already carried its non-periodic one: `OneD { periodic_axis: usize }`. Matches
773
+ on the bare variant need `OneD { .. }`. It also retires two `expect()`s in
774
+ `vibrations.rs` that were re-deriving the axis from the lattice.
775
+
776
+ - **`ko_ewald::warn_one_dimensional_electrostatics_once` is gone.** It warned
777
+ that a wire's electrostatics were a truncated sum; they are not any more. A
778
+ no-op stub was rejected on purpose — it would read as a warning still watching
779
+ for something.
780
+
781
+ - `--freq` prints the `3N - n` **internal** modes, not all `3N`, with new
782
+ `n_projected_translations`, `n_projected_rotations` and `n_internal_modes`
783
+ lines. `imaginary_modes` counts every `f < 0` — the old `-10 cm^-1`
784
+ threshold existed to hide projected residues, which no longer occur, so it
785
+ now only hid real saddles.
786
+ - **`nn_train --dataset` is now REQUIRED and has no default.** Training data is
787
+ supplied by the caller; a default would name a path most callers do not have,
788
+ and would let a run silently pick up whatever happened to sit there. The
789
+ refusal names the flag and points at `docs/nn-xtb.md`, which now carries
790
+ the `.gfn2ds` format and a worked training procedure. The four corpus tests in
791
+ `src/nn/dataset.rs` take their directory from `GFN2_NN_DATASET` and print
792
+ `SKIPPED` when it is unset. NN-xTB training records are excluded from the
793
+ source archive as well as from the crates.io and PyPI packages, so they are
794
+ distributed in no channel at all.
795
+ - `scripts/make_release_zip.py` gains `--lf` (LF endings, no BOM, working tree
796
+ untouched).
797
+ - **`--gradients` and `--stress` now imply `--electronic`.** They did not, and
798
+ a run with either alone printed no gradient at all; see *Fixed* above.
799
+ Nothing that produced output before changes.
800
+ - **The default SCC starting guess is the EEQ-BC charges**
801
+ (`InitialGuess::Eeq`), matching what xtb and tblite do. `InitialGuess::Zero`
802
+ reproduces the pre-0.6.1 path. Measured: this alone does not change any
803
+ converged energy in the oracle suite, but it is the right basin to start a
804
+ polar reference from.
805
+ - **`--opt-cell volume` is refused on a slab or a wire**, by name: the cell
806
+ volume of a partially periodic cell is set by the vacuum thickness the input
807
+ chose, so constraining it constrains nothing physical.
808
+ - **Slab and wire stress components touching a non-periodic axis are reported
809
+ as zero**, in the CLI, the Python bindings and the ASE calculator alike.
810
+ `unmasked_stress_hartree_per_bohr3` has the raw tensor. An ASE
811
+ `FrechetCellFilter` on a slab therefore no longer relaxes the vacuum.
812
+
813
+ ## [0.6.0] - released
814
+
815
+ ### Removed (BREAKING)
816
+
817
+ - **The Ln-xTB lanthanide parameters are no longer distributed**, in any channel.
818
+ Removed: `data/param_lnxtb.txt` (and with it the `include_str!`, the
819
+ `BUNDLED_LNXTB_OVERLAY_TEXT` public constant and its `lib.rs` re-export),
820
+ `data/lnxtb_reference_bondlengths.tsv`, `examples/lnxtb/` (50 `.xyz` files),
821
+ and `tests/lnxtb.rs`.
822
+
823
+ **Why.** The values come from the Supporting Information of a subscription
824
+ article. Wiley's Online Library terms of use state that users "shall not have
825
+ the right to incorporate any material from the Electronic Products into any
826
+ institutional or other repository", and v0.6.0 publishes to GitHub, crates.io
827
+ and PyPI - three repositories, distributing continuously. That is a contract
828
+ term, and attribution does not cure a contract term; only not distributing
829
+ does. The earlier assessment (`docs/lnxtb_licensing.md`) recommended keeping
830
+ the data with stronger attribution, and that was right for its premise: the
831
+ deliverable then was a zip handed to one person. The premise changed, so the
832
+ conclusion did. Nothing about the copyright analysis changed - it still rates
833
+ every copyright theory as very low, and Wiley has no copyright standing in
834
+ Supporting Information at all (its CTA excludes SI from the assignment; the
835
+ author retains it).
836
+
837
+ **Migration.** `scripts/extract_lnxtb_params.py` (new) builds the parameter
838
+ file from your own copy of the Supporting Information. This is a transcription
839
+ step, not a reimplementation: SI Table S62 already prints the parameters in
840
+ exactly the xtb `$Z=` syntax, as extractable text. Verified to reproduce the
841
+ previous hand transcription exactly - 15 blocks, 300 values, `identical`.
842
+
843
+ ```bash
844
+ python scripts/extract_lnxtb_params.py --si jcc70321-sup-0002-datas2.pdf -o param_lnxtb.txt
845
+ gfn2_rs --xyz NdF3.xyz --electronic --ln-xtb-param param_lnxtb.txt --charge 0 -u 2
846
+ ```
847
+
848
+ **API changes.** `Gfn2Parameters::bundled_lnxtb_overlay()` is gone;
849
+ `apply_lnxtb_overlay()` now takes `Option<&str>` and resolves
850
+ `--ln-xtb-param PATH` > `GFN2_LNXTB_PARAM` > **error**, mirroring the existing
851
+ `--spin-param` precedent. There is deliberately no fallback to plain GFN2: a
852
+ silent fallback returns a converged result that looks like Ln-xTB and is not.
853
+ New CLI flag `--ln-xtb-param FILE`; new Python kwarg `ln_xtb_param=`.
854
+
855
+ **What this costs, stated plainly.** `tests/lnxtb.rs` validated all 15
856
+ lanthanides against the SI energies (13 of 15 at 1e-7..1e-9 Eh; Pr/Sm are a
857
+ documented parameter-set instability) and the LnF3 bond lengths. Every one of
858
+ its seven tests loaded an SI geometry, so none survive the fixtures. Those
859
+ measurements are recorded in the docs but are no longer re-runnable from this
860
+ repository. The overlay *wiring* is still regression-tested, on a synthetic
861
+ two-element parameter block (`src/params.rs`), because parsing the `5d6s6p`
862
+ shell shape and replacing exactly the listed elements do not depend on which
863
+ numbers arrive; and `src/electronic/tests.rs` keeps a lanthanide SCC test on
864
+ the bundled GFN2 block.
865
+
866
+ The SI also tabulates a separate Ce(VI) re-optimization for the `Ln-xTB*`
867
+ method. The extractor reports it and can select it with `--ce6`; the previous
868
+ bundled file silently contained only the Ln-xTB Ce block.
869
+
870
+ ### Fixed
871
+
872
+ - **`nn_train --checkpoint-every` wrote nothing under `--select-by holdout`.**
873
+ The checkpoint block only fired `if let Some(w) = &best_weights`, and
874
+ `best_weights` is populated by the training-loss branch (`!select_by_holdout`)
875
+ or by a holdout evaluation. With holdout selection the first evaluation is at
876
+ `--holdout-every` epochs, so `--checkpoint-every 10 --holdout-every 25` ran the
877
+ checkpoint block at epochs 9 and 19 and silently wrote no file. Caught on a
878
+ live 100-epoch run: 13 epochs (7.3 h) in, no `.gfn2nn` existed.
879
+ The wording ("save the best model every N epochs") is not violated -- there is
880
+ no "best" yet -- but the effect is a long run's first hours having no crash
881
+ protection and nothing saying so.
882
+
883
+ Now the block falls back to the CURRENT weights when no selected best exists,
884
+ so `--checkpoint-every` always produces a resumable file, and a startup line
885
+ states when the first *selected* checkpoint will appear. Selection semantics
886
+ are unchanged: the final save still ships `best_weights` when they exist.
887
+
888
+ - **Ewald splitting-parameter independence is now enforced, not just hoped for.**
889
+ `physics::ewald_splitting_parameter_independence` had been `#[ignore]`d since
890
+ it was written -- first as a known failure, then as "partially fixed" -- and
891
+ the skip reason claimed the SCF total still drifted by 8.1e-3 Eh. Running it
892
+ as part of a sweep of every ignored test showed that it PASSES, and by a wide
893
+ margin: 2.7e-15 Eh spread (5.3e-16 relative) over alpha = 0.16 .. 0.32
894
+ bohr^-1, reproducible to every printed digit. The regularized `G = 0` term
895
+ for the `r^-3` Klopman-Ohno tail had closed the defect; nobody noticed because
896
+ the test stayed disabled. It is now enabled, which makes alpha independence a
897
+ standing guard over the whole periodic electrostatic assembly (isotropic SCC,
898
+ AES and D4 at once) instead of a disabled note, and the Known-issues entry
899
+ claiming otherwise has been withdrawn.
900
+
901
+ Sweep result for the record: all **42** ignored library tests pass
902
+ (`cargo test --release --lib -- --ignored --test-threads=1`, 303 s). The
903
+ remaining disabled tests are the two documented in Known issues
904
+ (`oracle_mindless03_metastable_reference`, the spin-polarized Mermin
905
+ identity), which fail for the reasons recorded there.
906
+
907
+ - **The standalone periodic multipole-Ewald tool computed the charge-quadrupole
908
+ channel with the sign inverted.** `multipole.rs` kept a private
909
+ `QTHETA_COEFF = -1/3` and used the ONE constant for both the real-space pair
910
+ kernel and the reciprocal-space structure factor. Those two spaces genuinely
911
+ need opposite signs -- Poisson summation flips this channel, which is why
912
+ `aes.rs` carries `AES_QTHETA_COEFF` (`+1/3`) and `AES_RECIP_QTHETA_SIGN`
913
+ separately -- so the shared constant was right for the reciprocal sum and
914
+ wrong for the real-space one.
915
+
916
+ **Scope: `gfn2_rs --multipoles FILE` on a PERIODIC system only.** The
917
+ molecular branch of that flag goes through `aes::direct_aes_potentials` and
918
+ was always correct, and the SCF, gradient, Hessian, third-derivative and
919
+ NN-xTB paths all import the shared constants, so none of them were affected.
920
+
921
+ Found by a mechanical sweep for constants defined in more than one module,
922
+ looking for the pattern that produced an earlier defect of exactly this kind
923
+ (a private duplicate of this same constant with a flipped sign inverted the
924
+ frozen q-theta Hessian block, ~9e-3 Eh/bohr^2, recorded above
925
+ `AES_QTHETA_COEFF`). This copy had no test at all: `multipole.rs` contained a
926
+ single test, for an unrelated projection convention.
927
+
928
+ The fix imports the shared constants instead of re-deriving them, and names
929
+ the two spaces separately (`QTHETA_COEFF`, `RECIP_QTHETA_COEFF`) so one cannot
930
+ silently serve both again. `standalone_multipole_kernel_agrees_with_the_scf_aes_sign`
931
+ now pins all three channels against `aes::direct_aes_potentials`, comparing
932
+ the unscreened real-space kernels rather than Ewald totals -- the AES damping
933
+ factors are strictly positive, so they cannot change a sign, which makes this
934
+ a decomposition-free oracle. Measured after the fix, the standalone/SCF
935
+ magnitude ratio is 1.2461 for q-theta and 1.2460 for mu-mu, i.e. exactly the
936
+ shared `f5` damping factor, with matching signs.
937
+
938
+ - **The two `nn_train` fast-route fallbacks now name the structure and the
939
+ reason.** Both discarded the route's error (`Err(_)`) and announced themselves
940
+ once, globally, as "some frequency/force structures are outside the domain" —
941
+ naming neither how many, nor which, nor why. On an eleven-hour run that is the
942
+ difference between a one-structure nuisance and half the set on the slow
943
+ route, and the slow route is the trainer's dominant cost.
944
+
945
+ Both messages had also gone **stale against the code they described**: the
946
+ frequency one listed "molecular references" among the causes when it is
947
+ PERIODIC references that are rejected, and the force one still claimed "closed
948
+ shell" after the open-shell adjoint landed. Printing the route's own error
949
+ cannot drift that way. Reported once per structure, since the route is
950
+ re-decided on every item and an unconditional message would repeat the whole
951
+ list once per epoch.
952
+
953
+ - **Per-item notes now reach `--log`, not just stderr.** The epoch line already
954
+ followed the rule that anything which removed data from the gradient belongs
955
+ in the log rather than the stderr stream; the notes that say where the epoch's
956
+ TIME went did not, and they are just as unrecoverable once an eleven-hour
957
+ run's console scrollback has rolled. `EpochMetrics` gained `messages`, and the
958
+ CLI writes them above the epoch line they belong to.
959
+
960
+ - `nn_train`'s slow-item warning now names the item. It previously reported only
961
+ that something had taken minutes, which on a 550-item epoch left no way to
962
+ tell which structures — or even which KIND of item — accounted for the time.
963
+
964
+ - **Every `#[allow(dead_code)]` in `src/` now either states why or is gone.**
965
+ `rustc` emits no warnings (verified over `--all-targets --all-features` with
966
+ the crate fully recompiled, which includes `src/python.rs` — the normal test
967
+ gate never compiles it), but fourteen suppressions were standing between that
968
+ clean output and the truth. Nine already carried a reason. The other five:
969
+
970
+ - `hessian::response`: two private routines removed —
971
+ `scalar_derivative_direct_pulay_hessian_kpoints` and
972
+ `scc_shell_response_gradient_fd`, with the four imports they alone used. No
973
+ caller anywhere in the crate, tests included. An FD oracle that no test
974
+ drives is not an oracle; it is unexercised code rotting against the routine
975
+ it was written to check.
976
+ - `hessian::response::scc_response_potential_ground_density_gradient`: kept,
977
+ with the reason added — it is dead in a lib build and live in a test one,
978
+ where it is the partner-FD arbiter for the shell response.
979
+ - `cphf::BlochCartesianCphfHessianResult`: the `allow` moved from the STRUCT
980
+ to the two fields that are actually test-only
981
+ (`orbital_shell_charge_responses`, `metric_shell_charge_responses`). A
982
+ struct-level suppression also hides every field added later; the other nine
983
+ fields here are live.
984
+ - `third_derivatives::classical::CnThirdData::blocks`: the `allow` was stale
985
+ and inert — the field IS read, by `finalize_d4`, for
986
+ `sum_p f_p d^3 CN_p / dR^3`. Removed the attribute and documented the
987
+ reader, so the field no longer reads as abandoned.
988
+
989
+ The remaining ~113 `#[allow(clippy::too_many_arguments)]` and
990
+ `#[allow(clippy::type_complexity)]` are left alone deliberately: both lints
991
+ are in `clippy::complexity`, which `Cargo.toml` already sets to `allow`
992
+ crate-wide, so they are inert today — but they are the defence if that
993
+ setting is ever tightened.
994
+
995
+ ### Documentation
996
+
997
+ - `docs/README.md` (new): an index. Seven documents, 345 KB, and nothing said
998
+ which to open — or which are maintained against the code (the two API
999
+ references) versus dated records of one investigation (everything else).
1000
+ - `docs/nn-xtb.md`: the **Results** and **Tried and rejected** sections
1001
+ were unfilled `<!-- -->` template placeholders in a document whose first
1002
+ paragraph says "start here before optimizing anything in this module". Both
1003
+ are now written from the measured record, including the two refuted
1004
+ hypotheses above and the parallel-scaling diagnosis, and the header points a
1005
+ reader at the refutations first.
1006
+ - The v0.5.0 engineering notes (retired from `docs/` in v0.6.1): status corrected from
1007
+ `[0.5.0] - in progress` to `released`.
1008
+ - `docs/measurements.md`: says explicitly that its absolute times are only
1009
+ comparable within its own series.
1010
+
1011
+ ### Added
1012
+
1013
+ - **`dfpt`: self-consistent linear response at a wavevector — the dynamical
1014
+ matrix `D(q)`.** New public module with `force_constants_at_q`,
1015
+ `dynamical_matrix_dfpt`, `frequencies_dfpt` and
1016
+ `frequencies_from_dynamical_matrix`, plus `DfptOptions` for the
1017
+ coupled-perturbed controls.
1018
+
1019
+ **`q = 0` is exact by construction.** The `q = 0` force constants ARE the
1020
+ relaxed analytic Hessian, so that path routes to `compute_hessian` rather
1021
+ than reimplementing it — a second implementation could only disagree, and a
1022
+ bit-identity test pins them together.
1023
+
1024
+ **A non-zero `q` runs a periodic CPHF on the PRIMITIVE cell** (`dfpt::solver`
1025
+ and its submodules), and reproduces the exact supercell fold at commensurate
1026
+ `q` having shared no code with it beyond the SCF and the pair integrals:
1027
+
1028
+ | reference | mesh | `q` | oracle | `max |C_DFPT - C_supercell|` | scale |
1029
+ |---|---|---|---|---|---|
1030
+ | H2 lattice (5 bohr period) | `[2,1,1]` | 1/2 | 2x1x1 | `6.8e-11` | 0.315 |
1031
+ | H2 lattice | `[3,1,1]` shifted | 1/3 | 3x1x1 | `1.5e-10` | 0.311 |
1032
+ | LiH lattice (polar) | `[2,1,1]` | 1/2 | 2x1x1 | `3.8e-15` | 0.037 |
1033
+
1034
+ with `|C(q) - C(0)| = 2.7e-2` / `7.6e-3` on the H2 lattice, so the tests
1035
+ cannot pass on a `C(q)` that dropped the phase. The whole `q`-machinery run
1036
+ AT `q = 0` lands on `compute_hessian` to `5.0e-14` (acoustic sum rule
1037
+ `1.7e-16`); `C(q)` is Hermitian to `1.3e-17` before the final symmetrization
1038
+ and `C(-q) = C(q)*` to `7.8e-18` at `q = 1/4`; an incommensurate `q = 0.31`
1039
+ gives real branches that move by `12 cm^-1` over `dq = 0.01`.
1040
+
1041
+ How it is built, and what pins each part (`dfpt::q_tests`):
1042
+
1043
+ * **Bands at `k + q`** from the converged Fock, by the same builder that
1044
+ rebuilds the mesh bands (`electronic::closed_shell_scf_points_at`), so a
1045
+ commensurate `q` lands on a mesh point's own bands. Integer occupations and
1046
+ a clear frontier gap over the union of both point sets are REQUIRED
1047
+ (linear response is not the right object at a degeneracy).
1048
+ * **The bare perturbation** is the `q = 0` Bloch derivative-matrix walk with
1049
+ one phase changed — `e^{i(k+q).T}` on the ket-centre contribution, since
1050
+ the ket atom sits in cell `T` and moves with that phase — including the
1051
+ coordination-number chain, whose site-resolved `dCN_a/du_c^(q)` follows
1052
+ the same bra/ket rule. Pinned at `q = 0` element for element against
1053
+ `cphf::bloch_cartesian_ao_derivative_matrices` (`3.3e-16`).
1054
+ * **The IES kernel at `q`** (`dfpt::kernel`): Klopman–Ohno residual with
1055
+ `e^{iq.L}`, and the `1/r` and `r^-3` Ewald sums over `G + q`. At `q != 0`
1056
+ there is nothing to regularise — the `G = 0` members are finite and are
1057
+ exactly the supercell's `G_s = q` vectors. Pinned by finite differences of
1058
+ its own value (gradient and Hessian, `~7e-11`), by independence of the
1059
+ Ewald splitting parameter (`5.5e-12`, after removing the same-site `L = 0`
1060
+ smooth-term Hessian that the real-space sum excludes — the α scan found
1061
+ that at `9.4e-3` while value and gradient already agreed to `1e-12`), and by
1062
+ the fold of the supercell's own shell kernel (`2.3e-15`).
1063
+ * **The response** is the unified band-pair quotient
1064
+ `D_pr = [(f_r - f_p) V_pr - (f_r e_r - f_p e_p) S_pr]/(e_r - e_p)` on
1065
+ `(k+q, k)` blocks, which carries the resonant, the antiresonant (bra at
1066
+ `k+q`) and the occupied-metric (`-f S_pr`, closed form) terms at once; the
1067
+ shell-space self-consistency is a dense direct solve `(I - chi K(q)) dq =
1068
+ source`, so there is no mixing to stall.
1069
+ * **The skeleton** is every fixed-density block phased term by term with one
1070
+ rule (`e^{iq.(cell_Y - cell_X)}` per ordered pair of cluster sites):
1071
+ repulsion (`q = 0`: `0.0`; folds: `2e-16`), the fixed-charge shell IES
1072
+ written as `delta_ab sum_c q q H_0 - q q H_q` (`1e-15`), and the direct
1073
+ Pulay/CN block (`3e-16` at `q = 0`, `9e-16` folded at `q = 1/3`).
1074
+
1075
+ **Refused BY NAME, never answered with a number missing a term:** D4 and AES
1076
+ (no phased kernels yet), 1D/2D cells (no phased Ewald there), spin-polarized,
1077
+ open-shell and Fermi-smeared references, time-reversal-folded meshes, a
1078
+ molecule, a `q` along a non-periodic axis, and nonsense solver controls. A
1079
+ `q` that is a reciprocal-lattice vector IS Gamma and takes the Gamma path.
1080
+ `DfptResult` now reports which route ran (`DfptRoute`), the frontier gap,
1081
+ the pre-symmetrization Hermiticity residual and the shell-space residual, so
1082
+ a comparison can assert what it compared. `frequencies_from_dynamical_matrix`
1083
+ diagonalises a complex Hermitian `D(q)` through its realification.
1084
+
1085
+ **A mesh premise worth stating.** An `n`-fold supercell at Gamma folds the
1086
+ primitive points `{0, 1/n, ...}`; this crate's unshifted Monkhorst–Pack mesh
1087
+ (`i/n - 1/2`) is that set for even `n` but NOT for odd `n` (`[3,1,1]` is
1088
+ `{1/2, +-1/6}`, the fold of the supercell's zone-boundary point), where
1089
+ `kpoint_shifted = true` restores it. The first `q = 1/3` comparison missed by
1090
+ `1.6e-3` for exactly that reason, and with the Gamma-centred mesh the
1091
+ residual is `1.5e-10`. The probe that states the premise as a test had to be
1092
+ written twice: closure of the mesh under `k -> k + 1/n` is NOT it (every
1093
+ equispaced mesh is closed under its own spacing — the first probe asserted
1094
+ otherwise and was refuted); the criterion is that every mesh coordinate be a
1095
+ multiple of `1/n`.
1096
+
1097
+ `LongRange::Off` drops the `G = 0` members at `q != 0`. Measured: it changes
1098
+ `C(1/2)` on the non-polar H2 lattice by `1.7e-6`, and it leaves a
1099
+ splitting-DEPENDENT kernel (those members are what balance the real-space
1100
+ sums), so it is a diagnostic at the shipped splitting, not a second kernel.
1101
+
1102
+ **`force_constants_at_commensurate_q` gives `C(q)` at any `q` a supercell can
1103
+ represent.** It is NOT DFPT and does not claim to be — it costs a supercell,
1104
+ which is the cost DFPT exists to avoid — but it is exact, and it is the
1105
+ oracle the `q != 0` DFPT path is measured against. The transform is
1106
+ `C(q) = sum_L Phi_ab(L) e^{iq.L}`, read off a supercell's Gamma Hessian.
1107
+ Paired with `dynamical_matrix_from_force_constants` it reaches wavenumbers
1108
+ through the same mass weighting the `q = 0` path uses.
1109
+
1110
+ Pinned by four identities rather than by one comparison: the `q = 0` fold of a
1111
+ 2x1x1 supercell reproduces the primitive cell's force constants **on a
1112
+ `[2,1,1]` k-mesh** to `1.9e-16` (the supercell/k-mesh equivalence — comparing
1113
+ against the primitive GAMMA Hessian instead is off by `1.9e-5`, which is the
1114
+ physical gap between the two samplings, and getting that wrong is the easiest
1115
+ way to "verify" a broken replication order); `C(q)` is Hermitian to `3.5e-18`
1116
+ and `C(-q) = C(q)*` exactly; an incommensurate `q` is refused; and an isolated
1117
+ molecule in a 12-bohr cell shows FLAT dispersion, its zone-boundary
1118
+ frequencies within `0.13 cm^-1` of Gamma — with a lower bound on that shift
1119
+ too, so the test cannot pass on a `C(q)` that dropped the phase.
1120
+
1121
+ Note for readers coming from a DFT code: GFN2 has no exact exchange, so two of
1122
+ the three complications a Hartree-Fock or hybrid DFPT carries — a non-local
1123
+ `J - K` kernel, and `sum_T |T|^-1` diverging at Gamma — do not arise. GFN2's
1124
+ kernel is the isotropic IES gamma, the third-order onsite term, AES and the
1125
+ D4 charge response, all charge-channel.
1126
+
1127
+ - **The open-shell analytic Hessian now covers PERIODIC references on a k-point
1128
+ mesh.** `cphf::spin_bloch`'s two-channel solver — which existed for the
1129
+ k-point TD gradient — now also returns the per-k AO density and
1130
+ energy-weighted responses, and those feed the same
1131
+ `hessian::cphf_bloch::bloch_cphf_response_hessian` the closed-shell mesh uses.
1132
+ Acceptance: H3 (uhf = 1) in a 12-bohr cell at kgrid [2,1,1] against the
1133
+ central FD of the analytic gradient, `3.2e-7`, beside a closed-shell H3+
1134
+ control on the same cell at `7.1e-9`.
1135
+
1136
+ Three things had to be fixed to get there, each of which the acceptance test
1137
+ found rather than a code review:
1138
+
1139
+ * `bloch_response_density` leaves `energy_weighted` at ZERO on the
1140
+ integer-occupation path (only the Mermin builder fills it), so every dW
1141
+ piece has to be assembled explicitly. `add_bloch_response_fock_energy_weighted`
1142
+ also hard-wires the isotropic-CHARGE convention; the spin fold is the same
1143
+ object at `+`/`-` per channel, so it is now split out as
1144
+ `add_bloch_shell_potential_energy_weighted`.
1145
+ * The occupied-metric fold reads the COMMON `h^x`, but a channel's `h^x` also
1146
+ carries the frozen reference spin fold `+/- 1/2 (V+V) S^x`, which
1147
+ `spin_bloch` applied only to the right-hand side. Invisible while nothing
1148
+ read `energy_weighted`; wrong as soon as something did.
1149
+ * Two guards refused the reference for reasons that did not survive reading
1150
+ the code: `hessian_pulay`'s k-point direct-Pulay walk takes every density
1151
+ through `KPointGradientBands::pair_contraction`, which already sums the
1152
+ channels, and `hessian::compute_hessian`'s open-shell branch shadowed the
1153
+ non-Gamma branch entirely, making the new route dead.
1154
+
1155
+ **A diagnosis worth keeping.** The first acceptance geometry was a
1156
+ near-equilateral H3 — the textbook Jahn-Teller system — whose alpha
1157
+ SOMO/LUMO are degenerate. Linear response is not the right object at a
1158
+ degeneracy: the CPHF denominator `eps_a - eps_i` collapses, the response
1159
+ block came out at `9.4`, and the Hessian missed the FD by `1.2e-3`. Nothing
1160
+ pointed at it except the geometry. A response-coefficient scan, a
1161
+ frozen-coefficient scan, and tightening the CPHF from `1e-8` to `1e-14` at
1162
+ 4000 iterations all left the residual at exactly `1.181e-3`; the solver's own
1163
+ charge response matched the SCF finite difference to ~7e-8 relative; and the
1164
+ `ao_responses` reduced BIT-IDENTICALLY to the closed-shell solver's on a
1165
+ split closed-shell reference. Distorting the geometry moved the response
1166
+ block to `0.079` and the residual to `3.2e-7`. `open_shell_kpoint_hessian_response_scale_scan`
1167
+ (ignored) reproduces every one of those probes.
1168
+
1169
+ Spin-POLARIZED meshes stay on FD, for a named missing piece: the three
1170
+ fixed-orbital spin blocks in Bloch form. They all carry `W` or `V = W m` and
1171
+ so vanish for UHF, which is why the UHF path is complete without them.
1172
+
1173
+ - **The open-shell analytic Hessian now covers Fermi-SMEARED references.** A
1174
+ two-channel Mermin CPHF (`cphf::spin_gamma`) sits beside the occupied-virtual
1175
+ amplitude solver, and the entry points pick between them on the occupations
1176
+ themselves. Three separate hard guards - the `hessian` dispatcher, the plain
1177
+ GFN2 `hessian::spin_response` assembly and the NN `nn::hessian_analytic`
1178
+ response folds - all bottomed out in the same amplitude parameterisation, so
1179
+ fixing it at the root lifted all three at once.
1180
+
1181
+ **Why it matters.** Of the 720 frequency structures in the training corpus, 108
1182
+ are open shell, and the small radical diatomics (CaBr, GaO, NSe) smear their
1183
+ degenerate pi levels straight into the old `1e-6` integer guard. Those
1184
+ structures were paying the semi-numerical route every epoch.
1185
+
1186
+ Two conventions are load-bearing and both were read off the code rather than
1187
+ assumed:
1188
+
1189
+ * The existing Fermi-Dirac occupation derivative is written for SPATIAL
1190
+ orbitals, `f in [0, 2]`; a spin channel holds `f in [0, 1]` and its
1191
+ derivative is `-f(1-f)/kT`, exactly TWICE the spatial one at the same
1192
+ physical filling. The two are now one expression, `-f(n-f)/(n kT)`, selected
1193
+ by the new `OccupationScale`, and the closed-shell path is bit-identical.
1194
+ * Alpha and beta each conserve their OWN electron count
1195
+ (`occupations::spin_resolved_occupations` fills them from two independent
1196
+ Fermi levels), so the chemical-potential shift enforcing
1197
+ `sum_p f_p^x = 0` is per channel. The channels couple through the charge and
1198
+ spin kernels, never through a shared `mu^x`.
1199
+
1200
+ Self-consistency is solved densely in the `(dq, dm)` shell space - one
1201
+ geometry-fixed `2 nshell` coupling matrix, one solve per perturbation - which
1202
+ is the closed-shell finite-temperature recipe with the magnetisation channel
1203
+ added. AES stays outside it, converged by the same damped outer loop the
1204
+ closed-shell path uses.
1205
+
1206
+ Acceptance: the full relaxed Hessian of a smeared OH radical (all terms) vs
1207
+ the central FD of the analytic gradient, UHF and spin-polarized. Note that the
1208
+ per-channel conservation test is deliberately NOT the magnitude check - a
1209
+ uniform factor of two cancels out of both `mu^x` and `sum_p f_p^x = 0`, so
1210
+ only a real second derivative can catch the wrong occupation scale.
1211
+
1212
+ - **The NN force-loss analytic adjoint now accepts open-shell references.** Both
1213
+ of its response solves route through the coupled two-channel spin CPHF, the
1214
+ same `nn::hessian_analytic::open_shell_response_folds` the analytic NN Hessian
1215
+ uses (generalised here to take the parameter perturbations directly, so the
1216
+ Hessian can pass `3N` of them and the adjoint the single `J_w` direction it
1217
+ actually needs). The family blocks need no spin work: every implemented head
1218
+ couples to a spin-summed quantity.
1219
+
1220
+ Until now an open-shell force structure fell back to the double finite
1221
+ difference every epoch, and the guard's stated reason - "the same carve-out
1222
+ the analytic NN Hessian documents" - had outlived that carve-out by a release.
1223
+
1224
+ Acceptance runs on TWO open-shell references, integer (H2O+) and Fermi-smeared
1225
+ (OH), against the same Richardson-extrapolated double FD the closed-shell path
1226
+ answers to. One is not enough for the reason this project already recorded:
1227
+ the broken closed-shell wiring passed H2O+ by accident while missing 5.6% of
1228
+ CH2's correction blocks.
1229
+
1230
+ **A real defect surfaced here, in the AES self-consistency tolerance.** At the
1231
+ `1e-8` copied from the closed-shell loop, the smeared case sat three decades
1232
+ above every other reference (`2.4e-8` against `1.7e-11`). A step scan settled
1233
+ what it was: FLAT from `5e-4` to `4e-3`, so neither `1/h` oracle noise nor
1234
+ `h^2` truncation, so a systematic error on the analytic side. At `1e-12` the
1235
+ residual is `1.3e-10` and the scan shows the expected noise-floor/truncation
1236
+ shape. The closed-shell loop keeps `1e-8` only because its own FD oracle
1237
+ floors out around there. Running the loop out is now an explicit error rather
1238
+ than a silently unconverged response.
1239
+
1240
+ - `nn_train --drop-heads A,B`: remove heads from the learned schema by full name
1241
+ (`global.gam3p`, `atom.rcov`, ...) before the network is built.
1242
+
1243
+ **Why.** A head that keeps hitting its `ClampSte` bound is a hard error for
1244
+ every item that touches it - deliberately, since a saturated STE gradient no
1245
+ longer matches the true derivative and the forces would not belong to the PES.
1246
+ In the v0.6.0 training run one head, `global.gam3p`, accounted for *all* 31
1247
+ saturation errors, and the plateau backoff chased it from `1e-4` down to
1248
+ `1.25e-5` before the guard's own "not rising" branch recognised that the clamp
1249
+ bound, not the step size, was binding. Dropping the head trains the remaining
1250
+ 38 at full step size instead of paying that tax every epoch. Combines with
1251
+ `--init-model`, which matches tensors by name and never asks the checkpoint
1252
+ for a head the reduced schema does not have, so a run can resume from a full-
1253
+ schema checkpoint with no loss of the other heads' learned state.
1254
+
1255
+ A name that is not in the schema is an **error**. `LearnSchema::without_heads`
1256
+ ignores unknown names by design, so an unvalidated typo would train the full
1257
+ schema while the log claimed the head was dropped - indistinguishable from a
1258
+ run that did what was asked. The removal is also recorded in the model
1259
+ header's `provenance` line, not only implied by the absent `head =` entry.
1260
+
1261
+ ### Changed
1262
+
1263
+ - **The NN-xTB out-of-domain guard no longer substitutes plain GFN2 on head
1264
+ saturation.** `NnFallbackPolicy` gained a [`FallbackMode`]: `Invalid` (the new
1265
+ default) substitutes only when the shifted parameters are UNUSABLE — a
1266
+ physical floor rejects them (`zeta < 0.05`, `GAM`/`REPA`), or the overlay
1267
+ breaks an SCC that plain GFN2 converges. `Extrapolation` restores the old
1268
+ behaviour; `Off` is what training uses.
1269
+
1270
+ Saturation evidence is still produced, on the new
1271
+ `NnEvaluation::extrapolation_warning`, so the guard trades a wrong
1272
+ substitution for a report rather than for silence.
1273
+
1274
+ **Why.** The old trigger conflated two different things. A saturated head
1275
+ gives a shift that is TRUNCATED but still correctly directed, which beats
1276
+ `Delta-p = 0`; genuine out-of-domain input gives a shift whose DIRECTION is
1277
+ arbitrary. Measured on the v0.6.0 training checkpoint, acting on saturation
1278
+ lost on four of seven benchmark families and won on one, and cost 44.640
1279
+ against 7.608 kcal/mol over all 2226 reactions. The structures it rejected
1280
+ were ordinary in-training-set ones (`force_ethanol_rmd17_*`, `sconf_*`).
1281
+
1282
+ - **A reaction energy is now computed with ONE treatment for all its species.**
1283
+ The guard is a per-STRUCTURE decision while a reaction energy is a DIFFERENCE
1284
+ of structures, so rejecting some species and not others differenced a
1285
+ corrected energy against an uncorrected one — producing errors larger than
1286
+ either method alone (S66 1.369 -> 43.998 kcal/mol, against plain GFN2's own
1287
+ 0.734 on the same 62 reactions, with a 1648 kcal/mol outlier neither method
1288
+ can produce). `nn_eval` now reports the whole reaction from the baseline if
1289
+ the guard rejected any species.
1290
+
1291
+ - `--nn-fallback` accepts `off | on | invalid | extrapolation` (`on` is
1292
+ `invalid`). New `--nn-element-coverage on|off`.
1293
+
1294
+ ### Added
1295
+
1296
+ - **`nn_train --energy-normalization none|wtmad2`.** `wtmad2` scales each
1297
+ reaction-energy RESIDUAL by `mean|E_ref| over all reactions / mean|E_ref| over
1298
+ its subset` — the GMTKN55 WTMAD-2 weighting, and the metric the NN-xTB paper
1299
+ reports its GMTKN55 result in.
1300
+
1301
+ Without it the 52 subsets enter a SQUARED kcal/mol loss with mean reference
1302
+ energies from 0.580 (RG18) to 568.7 (W4-17), a factor of 980, so the
1303
+ atomization sets take the gradient budget and a sub-kcal error on a weakly
1304
+ bound complex is invisible. That is measurable in the trained model: against
1305
+ plain GFN2 it wins on every subset whose own error is large (W4-17 190.3 ->
1306
+ 43.6, W4-11 120.7 -> 37.6) and loses on every subset whose error is small
1307
+ (RG18 0.112 -> 0.398, ACONF 0.194 -> 1.677, S66x8 0.591 -> 1.174), crossing
1308
+ over at a plain-GFN2 MAE of 2-3 kcal/mol.
1309
+
1310
+ The scale multiplies the residual, not the loss, so the Huber knee sits at the
1311
+ same RELATIVE error for every subset. Reported holdout MAE stays in kcal/mol
1312
+ — the option changes what the loss weighs, never what the metric measures.
1313
+ Default `none`, which is what every run up to v0.6.0 used.
1314
+
1315
+ - **`train_elements` in the `.gfn2nn` header**, and
1316
+ `NnCalculator::train_elements`. The trainer records the atomic numbers of its
1317
+ TRAIN split (not the whole dataset: a held-out structure contributed no
1318
+ gradient), and an evaluation falls back when asked about an element the model
1319
+ never saw. The key is optional on read, so no `FORMAT_VERSION` bump — an older
1320
+ build skips it into `unknown_keys`. A model that does not record it cannot be
1321
+ checked and is skipped rather than passed.
1322
+
1323
+ - **`rmd17_conformers.gfn2ds`** (515 reactions) and
1324
+ `scripts/rmd17_relative_energies.py` that generates it. `forces*.gfn2ds`
1325
+ carries 2150 structures that fed ONLY the force loss; for frames of the same
1326
+ molecule the reference energy DIFFERENCE is a pure conformational energy, and
1327
+ the format already expresses one — as a `$reaction`. No new geometry: the 516
1328
+ ethanol frames were matched to `rmd17_ethanol.npz` on coordinates (516/516, a
1329
+ worst deviation of exactly 0.0) and paired consecutively. Same PBE/def2-SVP
1330
+ level as the forces already in those records.
1331
+
1332
+ - **Published packages no longer carry the NN-xTB training dataset.**
1333
+ `Cargo.toml` gained an `exclude` (mirrored in `pyproject.toml`) covering
1334
+ the training-records directory and `tests/**`. The dataset is 11 MB and is read at run
1335
+ time by path rather than compiled in, so excluding it costs the published
1336
+ crate no functionality. It stays in the repository, where the trainer
1337
+ and the dataset tests use it. `cargo package --list` now ships two `data/`
1338
+ files, both `include_str!`-compiled and both explicitly GPL-compatible.
1339
+ - `src/nn/dataset.rs`: the four tests that load the bundled dataset now print
1340
+ `SKIPPED` with a reason and return when no corpus is available, instead
1341
+ of panicking. They run in full in the repository, so the release gate is
1342
+ unaffected; this only matters when running `cargo test` from a published sdist.
1343
+ - `THIRD_PARTY_NOTICES.md` gained a preamble stating that **bundled third-party
1344
+ data keeps its own licence and is not covered by this project's GPL grant**,
1345
+ with a per-artefact table naming which rows are and are not. GPL-3.0 § 5
1346
+ requires the covered work to be licensed as a whole under the GPL, so shipping
1347
+ non-relicensable material inside it without saying so would purport to grant
1348
+ rights nobody here holds. The project's own `docs/lnxtb_licensing.md` § 4 had
1349
+ identified this; the notices had not yet followed.
1350
+
1351
+ ### Performance
1352
+
1353
+ - **The encoder second-order pass is now parallel over its `3N` direction
1354
+ columns** (`nn::hessian_analytic::encoder_jacobian_and_curvature`). On a
1355
+ profiled epoch this one scope was **81% of `nn::analytic_hessian`** and the
1356
+ largest single scope in the trainer, at up to 23.7 s in ONE call — and it was
1357
+ a plain serial loop, in a file that contained no `rayon` at all.
1358
+
1359
+ Measured on an identical workload (same 24 items, same seed, same weights,
1360
+ `RAYON_NUM_THREADS=3`):
1361
+
1362
+ | scope | before | after |
1363
+ |---|---|---|
1364
+ | `nn::encoder_second_order` | 49,121 ms | **29,075 ms** (−41%) |
1365
+ | `nn::analytic_hessian` | 60,402 ms | **40,054 ms** (−34%) |
1366
+ | largest single call | 23,661 ms | **14,147 ms** (−40%) |
1367
+
1368
+ The unchanged scopes in the same runs moved by −11% to +18% (`nn::evaluate`,
1369
+ `nn::frozen_head_gradients`, `nn::train_compute_structure`,
1370
+ `nn::force_adjoint_param_grads`), which is the load noise of measuring on a
1371
+ machine that is also training; −41% sits outside that band.
1372
+
1373
+ This matters more than the percentage suggests. The trainer parallelizes over
1374
+ ITEMS and an epoch ends when its LAST item does, so a lone slow frequency
1375
+ structure leaves the machine idling on one thread while it finishes — the
1376
+ epoch's wall time is bounded below by exactly this loop, and the measurement
1377
+ above was taken with only 3 threads to spread it over.
1378
+
1379
+ **Bit-identical, by construction.** Every column reads only shared immutable
1380
+ state (the tape, the encoder graph, the constant adjoint seeds) and produces
1381
+ only its own Jacobian column and its own column of the curvature; `Tape` is a
1382
+ plain `Vec<Node>` with no interior mutability and all three of its traversals
1383
+ take `&self`; neither `nn::tape` nor `nn::mace` uses rayon, so no inner
1384
+ reduction order can shift. Columns are collected in index order and no sum is
1385
+ reassociated.
1386
+
1387
+ - **Two performance hypotheses were measured and REFUTED**, recorded here
1388
+ because both are the obvious thing to try again:
1389
+
1390
+ 1. *"Frequency items are slow because they fall back to the semi-numerical
1391
+ Hessian; the analytic route is the fast one."* On 15 real structures across
1392
+ four size bands (6, 10, 15 and 20 atoms) the two routes are within noise of
1393
+ each other — speedups 0.7×–1.7×, **mean 1.13×, with no size trend**. The
1394
+ analytic route buys about 10%, not the `6N`-versus-`O(1)` win the shape of
1395
+ the two algorithms suggests, because it pays `3N` parameter-direction
1396
+ responses of its own. The six largest frequency structures were also all
1397
+ closed-shell and all already on the analytic route, so the smeared/
1398
+ open-shell work did not move this population at all.
1399
+ 2. *"Force items are slow because the double FD pays `6N` displaced SCCs."*
1400
+ It does not: the force loss differentiates along ONE direction `w`, so the
1401
+ double FD is **two** displaced evaluations regardless of `N`. Measured
1402
+ through the production `run_item` path, the analytic adjoint is **9×
1403
+ slower** at 27 atoms (49.4 s vs 5.3 s) and 3.4× slower at 9 atoms — and it
1404
+ gets worse with size. `FORCE_ANALYTIC_STATE`'s cached decision to keep the
1405
+ double FD is correct, and increasingly correct as structures grow.
1406
+
1407
+ Both censuses are kept as ignored tests, so the numbers can be re-taken rather
1408
+ than re-argued: `tests/nn_freq_route_cost.rs` and
1409
+ `nn::train::tests::force_route_cost_census`.
1410
+
1411
+ - **`global.kdiff` is a dead head: it costs 25,217 learnable scalars (2.5% of
1412
+ the model) to produce a provably-zero output.** Recommend adding it to
1413
+ `--drop-heads` on the next training run; the accuracy cost is exactly zero.
1414
+
1415
+ `kdiff` is the paper's `k_pol`, read ONLY by the non-valence arms of
1416
+ `scc_common::h0_scaling`. `basis.rs` sets `is_valence = !seen_l[l]`, so a
1417
+ shell is non-valence only when its atom carries an earlier shell of the same
1418
+ angular momentum — and **no GFN2 element does**, verified across all 86 in the
1419
+ bundled parameters. Those arms are dead code for every system, `dE/d kdiff` is
1420
+ identically zero, and the head's zero-initialized final layer can never move.
1421
+
1422
+ Three independent measurements agree, on the 32-epoch checkpoint of the live
1423
+ run: the head's output is exactly `0.000000` over 400 sampled structures; its
1424
+ `w3` and `b3` are the only bit-zero final layer among 35 heads (every other
1425
+ head's `w3` reached `7.2e-2`–`4.3e-1`); and the structural argument above.
1426
+ `nn::param_grad`'s `d_shell_global_gradients_match_full_scf_fd` already stated
1427
+ this in a comment — nothing checked it, so the premise is now pinned by
1428
+ `every_gfn2_element_has_distinct_shell_momenta`, which fails loudly if a
1429
+ parameter set ever makes `kdiff` live.
1430
+
1431
+ - New diagnostics for the trainer's head stack, both ignored, both reading a
1432
+ checkpoint without disturbing a running trainer: `nn_head_saturation_census`
1433
+ resolves the epoch line's single `sat` number to a per-head maximum with the
1434
+ element and structure that reach it, and `nn_head_final_layer_census` reports
1435
+ which heads never left their zero init. The `sat` monitor is the right alarm
1436
+ and the wrong diagnosis — it says a head is pressing its clamp without saying
1437
+ which, so the only responses available were guesswork or nothing.
1438
+
1439
+ - **The shift bound uses ONE global RMS scale per head, which is wrong for any
1440
+ parameter whose per-element values span orders of magnitude.** `Δp_a =
1441
+ B(x_a) × scale`, with `B` clamped to `[−0.5, 0.5]` and `scale` the RMS of
1442
+ that parameter over all 86 elements (`LearnSchema::denorm_scales`). Measured
1443
+ as `0.5 × RMS` against the smallest non-zero baseline: **DPOL 128.9× (Rh),
1444
+ QPOL 96.0× (Pb), GAM3 43.1× (La), REPB 20.4× (He)** — four of six atom-wise
1445
+ families. `GAM` (1.6×) and `REPA` (1.2×) are fine.
1446
+
1447
+ For the common organic elements the DPOL bound permits ±0.11× of baseline on
1448
+ F, ±0.16× on H, ±0.18× on O — but **±2.20× on carbon**, whose baseline
1449
+ (−0.4117) is an order below the RMS. Carbon is also the most abundant
1450
+ training element and dominates the force loss, so the head with the most data
1451
+ and the loosest effective bound is the one that saturated. The `gam3` clamp
1452
+ saturation seen earlier in v0.6.0 training has the same root cause.
1453
+
1454
+ **The obvious fix makes it worse.** Scaling by each element's own `|p0|`
1455
+ tightens carbon's bound 4.4× (0.9047 → 0.2059) against a network that wants
1456
+ `|Δp| ≈ 0.896`, so it pins the head harder. It corrects the physical size of
1457
+ the shift and does nothing for the saturation, because `NnFallbackPolicy`
1458
+ keys on head-output saturation, not on the parameter. The two are separate
1459
+ defects and were conflated once here before being measured.
1460
+
1461
+ - `nn_eval` gained **`--nn-fallback on|off`** (default `on`), matching
1462
+ `gfn2_rs --nn-fallback`. `nn_train` evaluates with the policy DISABLED, so
1463
+ for a saturated model the training log's holdout MAE and `nn_eval`'s were
1464
+ different quantities with no way to compute one from the other. Verified on
1465
+ the same 223 held-out reactions: `off` reproduces the training log exactly
1466
+ (**9.053 kcal/mol**, plain GFN2 20.081), `on` gives **48.543** because 990 of
1467
+ 6391 structures fall back to plain GFN2 and a reaction then differences a
1468
+ corrected energy against an uncorrected one. The gap is the saturation
1469
+ damage, and the rejected structures are ordinary in-training-set ones
1470
+ (`force_ethanol_rmd17_*`, `sconf_*`) — the guard is misfiring, not catching
1471
+ unseen chemistry.
1472
+
1473
+ ## [0.5.0] - released
1474
+
1475
+ Two documents accompanied this entry when v0.5.0 shipped: a feature-by-feature
1476
+ walkthrough, and engineering notes keeping the full development record — the
1477
+ per-defect forensics, the evidence chains and every measured residual — that
1478
+ this summary condenses. Both were retired from `docs/` in v0.6.1 to cut a
1479
+ documentation set that had grown to 17 files; this entry is what remains of
1480
+ them. The one part not derivable from the code — where the NN-xTB
1481
+ implementation deliberately departs from the paper it is based on — was carried
1482
+ forward and is in `docs/nn-xtb.md`.
1483
+
1484
+ ### Highlights
1485
+
1486
+ - The **analytic Hessian is exact and is now the default**, for closed-shell,
1487
+ open-shell and spin-polarized references alike; five independent defects that
1488
+ had been masking each other were root-caused and fixed.
1489
+ - **Every production finite difference is gone** from the energy/gradient/Hessian
1490
+ path: AES and D4 potential derivatives, the NN-xTB parameter gradients
1491
+ (including the basis-moving Slater exponents) and all 22 learnable globals now
1492
+ have closed forms.
1493
+ - **TD-GFN2 excited-state gradients are exact** on the Gamma, k-point and
1494
+ open-shell paths, AES included — the k-point path was ~7.5e-2 Eh/bohr wrong
1495
+ and hidden behind a 1e-1 test tolerance.
1496
+ - **NN-xTB is end to end**: periodic evaluation and forces, excited-state
1497
+ gradients, a frequency (Hessian) loss, an out-of-domain fallback, parameter
1498
+ distillation, and a training set grown from 46 to 264 reactions plus 96
1499
+ structures carrying real reference forces.
1500
+ - **Divide-and-conquer SCC** gained analytic gradients (molecular,
1501
+ Gamma-periodic and k-point meshes — the Bloch driver runs a per-k DC step
1502
+ with one global Fermi level), the fixed-partition analytic stress (Gamma and
1503
+ k-point; the frozen-partition strain FD matches to ~1.4e-9 Eh/Bohr³) and a
1504
+ CLI/Python surface — with a measured, honest verdict on what it does and does
1505
+ not make linear.
1506
+ - **Analytic third derivatives** (molecular only, by design) are complete for
1507
+ molecular systems: classical, fixed-density electronic, the full 2n+1 CPHF
1508
+ response cross terms, the D4 charge response and the AES block (frozen and
1509
+ response) — water 8.681e-10 / water dimer 3.334e-9 Eh/bohr^3 against the
1510
+ five-point FD of the relaxed analytic Hessian. Retrievable as `dense`,
1511
+ `block` or `vec` from Rust, `cubic_check` and Python. The D4 **three-body
1512
+ (ATM) charge response** landed too, so nothing in the module is staged and
1513
+ `include_d4` no longer contracts for `enable_d4_atm = false`.
1514
+
1515
+ ### Method and correctness
1516
+
1517
+ - **SCC convergence.** New default accelerator: Fock-space ADIIS -> CDIIS
1518
+ (`--scf-accel`, `scf_accelerator=`), same fixed points as the legacy
1519
+ charge-space path (1e-15..1e-13 Eh) in roughly half the iterations (water
1520
+ dimer 29 -> 12, ferrocene 32 -> 15, PrF3 300 -> 64), with a divergence guard
1521
+ that hands pathological cases back to the legacy path. Gamma and Bloch;
1522
+ spin-polarized and LC keep the legacy accelerators.
1523
+ - **Parameters.** `param_gfn2-xtb.txt` is bundled and used by default
1524
+ (resolution order `--param` > `GFN2_XTB_PARAM` > bundled). The experimental
1525
+ **Ln-xTB** lanthanide overlay (`--ln-xtb`) reproduces the reference total
1526
+ energies to 1e-7..1e-9 Eh across the series; Pr/Sm halides have a documented
1527
+ parameter-side instability. `docs/lnxtb_licensing.md` records the licensing
1528
+ assessment for the bundled SI-derived data.
1529
+ - **xtb oracle validation.** 18 geometries and 170 reference values transcribed
1530
+ from the xtb unit tests; water energy components agree to 1e-11..1e-13 Eh and
1531
+ the analytic gradient is FD-exact to 2.9e-10 Eh/bohr.
1532
+ - **Analytic Hessian.** `HessianMode::{Analytic, SemiNumerical, FiniteDifference}`
1533
+ with `Analytic` the default and `HessianResult::mode_used` reporting what
1534
+ actually ran (also exposed to Python/ASE). All terms on, against the central
1535
+ FD of the analytic gradient: water 5.9e-8, CO2 3.7e-7, NH3 6.7e-8, H2 Gamma
1536
+ cell 7.6e-9, H2 k-grid [2,1,1] 7.6e-9 Eh/bohr^2. Open-shell UHF 6.6e-8 and
1537
+ spin-polarized 6.5e-8 via a new coupled two-channel spin CPHF.
1538
+ - **Spin derivatives completed.** The spin-polarized gradient was missing the
1539
+ Mulliken-Pulay overlap chain of `E_spin = 1/2 m^T W m` (1.8e-4 -> 1.2e-9
1540
+ Eh/bohr), and spin-polarized **stress** now works (its virial agrees with the
1541
+ frozen-density strain FD to ~9 significant digits).
1542
+ - **TD-GFN2 excited-state gradients.** Gamma with full AES (~1e-9 vs energy FD);
1543
+ k-point rebuilt on the direct method (singlet 3.3e-8, triplet with AES
1544
+ 3.8e-8, replacing a ~7.5e-2 error); open-shell rebuilt likewise (1.5e-11
1545
+ without AES, 1.2e-11 with).
1546
+ - **Analytic third derivatives** (`compute_third_derivatives`, `cubic_check`),
1547
+ **molecular only by design**: classical repulsion 4.9e-12 and D4 pair+ATM
1548
+ ~3e-15, electronic fixed-density H2 4.5e-13 / water 2.1e-13, against the
1549
+ five-point FD of the analytic Hessian. The **2n+1 CPHF response cross
1550
+ terms** are implemented for SCC-only references (IES gamma +
1551
+ third/fourth-order onsite + H0 CN chains; `include_response`): the full
1552
+ relaxed `E^xyz` matches the five-point FD of the relaxed analytic Hessian
1553
+ at the FD floor — H2 3.3e-11, water 1.8e-11 Eh/bohr^3 — with every CPHF
1554
+ convention pinned by four dedicated response tests
1555
+ (`third_derivatives::cphf3`). The **D4 charge-response cross terms** are
1556
+ implemented too (`include_d4`), on new third-order D4 charge machinery
1557
+ (`d3zeta`, mixed q/CN reference weights): water reaches 1.2e-10 against the
1558
+ D4-relaxed Hessian FD with the two-body pair term. The **frozen-multipole AES
1559
+ block** (`include_aes`, `third_derivatives::aes3`) is the exact third
1560
+ derivative of the energy `aes_frozen_multipole_hessian` is the second
1561
+ derivative of — damped multipole kernels plus the logistic damping-radius
1562
+ CN chain in a `Local3<5>` jet, composed by the general multivariate chain
1563
+ formula — and matches that Hessian's five-point FD at the rounding floor
1564
+ (water 4.7e-13, water dimer 7.2e-13 Eh/bohr^3). The **AES moment-RESPONSE
1565
+ cross terms** (`third_derivatives::aes_response3`: CAMM Pulay folds of the
1566
+ second and third moment-integral derivatives, the AES kernel against one and
1567
+ two multipole responses, the second density variation against the AES
1568
+ response Fock, and the onsite-polarization channel) complete the relaxed
1569
+ tensor. With everything on, the analytic `E^xyz` matches the five-point FD of
1570
+ the relaxed analytic Hessian at **water 8.681e-10** and **water dimer
1571
+ 3.334e-9** Eh/bohr^3 (gate 1e-6). The **DFT-D4 three-body (ATM) charge
1572
+ response** completes the module: the ATM energy is `W(geometry) x C9` with
1573
+ `C9 = sqrt(C6_ij C6_ik C6_jk)` the only charge-dependent factor, so its
1574
+ whole charge response is a third-order jet of `C9` in the six variables
1575
+ `(q_i, q_j, q_k, covCN_i, covCN_j, covCN_k)` (built from the log
1576
+ derivatives of the three pair `C6`s) contracted with the geometric factor
1577
+ and its first two Cartesian derivatives — the `V^{yz}` / `K^x` / `K'`
1578
+ classes the pair term already had, deposited through per-atom accumulators
1579
+ so the per-triple work stays O(1) in the atom count. `include_d4` therefore
1580
+ no longer contracts for `enable_d4_atm = false`, and with the three-body
1581
+ term on the tensor reaches **water 1.789e-11** and **water dimer 9.844e-9**
1582
+ Eh/bohr^3 against the D4-relaxed Hessian FD. Because the GFN2 damping makes
1583
+ the three-body term small, the acceptance is backed by two sensitivity
1584
+ guards: the `C9` jet against its own step-halved finite differences, and
1585
+ the full dimer comparison rerun with `s9` scaled by 1e4 — four decades of
1586
+ amplification of the three-body classes alone, which leaves the residual at
1587
+ the FD floor (9.728e-10).
1588
+ - **Third-derivative output forms** (`ThirdDerivativeRequest`): the tensor can
1589
+ now be retrieved as `dense`, `block` or `vec`, all three accumulating into the
1590
+ *same* sink so they agree to round-off where they overlap
1591
+ (`dense[a][b][c] == block(c)[(a,b)] == vec(e_c)[(a,b)]`, pinned by
1592
+ `third_derivatives::tests`; the block/vec pair is bit-identical).
1593
+ `ThirdDerivativeResult::dense_tensor()` materializes the plain row-major
1594
+ `3N x 3N x 3N` buffer so callers never hand-compute `third_index`;
1595
+ `ThirdDerivativeRequest::block(coords)` returns the symmetric `3N x 3N` slices
1596
+ `M^(c)_ab = E_abc` a cubic-force-field / anharmonic consumer wants, without
1597
+ ever materializing `3N^3`; `vec` (the `ModeContractions` variant, now also
1598
+ reachable as `ThirdDerivativeRequest::vec`) stays the primary large-system
1599
+ API. Only `dense` is capped (`3N <= 150`) and its error now quotes the actual
1600
+ byte cost of both stores. The molecular-only, closed-shell and
1601
+ integer-occupation guards are identical for every form. Wired through
1602
+ `compute_third_derivatives`, `cubic_check` (`--form dense|block|vec`,
1603
+ `--block-coords`, `--vectors FILE`, `--compare-forms`, `--no-fd`, and `--out`
1604
+ now writes the selected form) and the new Python
1605
+ `Gfn2NativeCalculator.third_derivatives(...)` / `ThirdDerivativeResult`.
1606
+ - **Fully-analytic NN-xTB Hessian** (`nn::hessian_analytic`, molecular): the
1607
+ complete Eq. 25 assembly with no finite differences — fixed-parameter CPHF
1608
+ Hessian, the encoder Jacobian and its forward-over-reverse curvature term,
1609
+ and the SCC mixed / parameter-parameter blocks. The `n_theta` CPHF solves
1610
+ the parameter-parameter block would need collapse to **3N** by solving one
1611
+ parameter-direction response per Cartesian direction
1612
+ (`cphf::solve_gamma_parameter_cphf_response`, which shares the geometric
1613
+ solver's operator and differs only in its right-hand side). Head families
1614
+ land one at a time behind an explicit guard — a schema containing an
1615
+ unimplemented head is REJECTED, never silently approximated. Covered so
1616
+ far: repulsion (`repa`, `repb`, `kexp`), the whole H0 band set (`lev`,
1617
+ `kcn`, `poly`, `en`, `enscale`, the six Wolfsberg scalings), the whole
1618
+ charge-kernel set (`gam`, `lpar`, `gam3`, `third`, the four `gam3*`
1619
+ globals, `alphaj`) and the DFT-D4 globals (`a1`, `a2`, `s8`, `s9`). Every
1620
+ family is accepted against the Richardson extrapolation of the
1621
+ semi-numerical reference at two step sizes, with a measured sensitivity
1622
+ guard proving the correction blocks are large enough for the test to mean
1623
+ anything; all sit at the reference's ~2-4e-9 floor.
1624
+ - **Divide-and-conquer SCC** (experimental): energy path plus analytic
1625
+ gradients for molecular AND Gamma-periodic references (single subsystem
1626
+ matches the exact gradient to ~1e-16; saturated buffers reach the ~1e-9 FD
1627
+ floor; the truncated-buffer partition-boundary residual is quantified rather
1628
+ than hidden). k-point and stress are explicit errors.
1629
+ - **Physics validation suite** (`tests/physics.rs`): translation/rotation
1630
+ invariance, gradient and Hessian sum rules, k-grid <-> supercell consistency,
1631
+ size consistency, charge conservation and spin electron counts, each asserting
1632
+ a stated tolerance and printing its measured residual.
1633
+
1634
+ ### NN-xTB (experimental throughout)
1635
+
1636
+ - **The analytic NN Hessian now covers the shipped schema head for head.**
1637
+ `atom.rcov` was the last gap, and it is the only head that reaches the energy
1638
+ through an INTERMEDIATE variable rather than through a factor of some term:
1639
+ it is the CN counting radius, so it moves the coordination number, and the
1640
+ coordination number has exactly two consumers - the H0 onsite level
1641
+ `h = hdiag - 0.1 kcn CN eV` and the AES damping radius `R(CN)`. (The D4 covCN
1642
+ is not one of them; it keeps its own frozen radii by design.)
1643
+
1644
+ Routing it as an EFFECTIVE CN TANGENT is what made the families tractable.
1645
+ The band family adds it to the per-shell onsite tangent it already builds for
1646
+ `lev` and `kcn`; the AES family carries it as a sixth radius axis, which its
1647
+ `RadiusJet` was already able to serve because `arg = CN - vcn - shift` makes
1648
+ `d/dCN == -d/dvcn` an identity to all orders. Everything CN-mediated then
1649
+ falls out of machinery that was already there and already tested, and only
1650
+ the two terms that differentiate `CN` ITSELF are folded separately against
1651
+ the CN adjoint: `d2CN/dR drcov` in the mixed block and `d2CN/drcov2` in the
1652
+ theta-theta block. One structural consequence: `h` is bilinear in the pair
1653
+ `(kcn, CN)`, so putting a parameter inside `CN` gives the band family's level
1654
+ factor a frozen second-parameter derivative it never had - a `kcn x rcov`
1655
+ corner that is identically zero unless both heads are in the schema.
1656
+
1657
+ Verified against two INDEPENDENT references, because one convention can look
1658
+ right on its own: a Richardson-extrapolated finite difference of the exact NN
1659
+ gradient (the true PES) reaches 1.5e-11 for `rcov` alone, 1.5e-11 with
1660
+ `shell.kcn`, 1.8e-11 with the AES radius heads - against correction blocks of
1661
+ 1e-4, so seven orders of relative accuracy; and a double finite difference of
1662
+ the head gradients through the force adjoint, which shares every family
1663
+ block, reaches 1.0e-10 and 1.1e-10 relative. The AES family's block-level
1664
+ test gained a fifth per-atom slot for it and holds at 5e-10 relative.
1665
+
1666
+ Consequences elsewhere: `tests/physics.rs` no longer has to drop `atom.rcov`
1667
+ from the Hessian fixtures, and the force adjoint covers the whole schema too
1668
+ (it shares `IMPLEMENTED_HEADS` precisely so a head cannot be covered by one
1669
+ assembly and not the other). Molecular references only, the same carve-out
1670
+ the AES and Slater-exponent families make - a periodic self-image pair has
1671
+ `rc = 2 rcov` and its radius chain doubles, which is refused rather than
1672
+ guessed.
1673
+
1674
+ - **Model and derivatives.** Periodic evaluation and **periodic forces**
1675
+ (1.9e-9 Eh/bohr vs the FD of the NN energy); excited-state gradients
1676
+ (`NnCalculator::tda_gradient`, 1.1e-8); encoder Hessian-vector products and
1677
+ the semi-numerical NN Hessian; the learnable set extended with `atom.en` and
1678
+ `shell.third`; analytic `dE/dp` for every class including the basis-moving
1679
+ Slater exponents and all 22 globals.
1680
+ - **Training.** The frequency (Hessian) loss completes the paper's Eq. 26 (its
1681
+ mode-projected adjoint matches a per-parameter FD to a relative 8e-5);
1682
+ `--init-model` warm starts, `--select-by holdout` stops shipping past the
1683
+ generalization optimum, and an SCC convergence ladder plus a saturation guard
1684
+ keep long runs alive.
1685
+ - **Training robustness** (each item measured against a real failure before it
1686
+ was built): the frequency loss now uses the **fully-analytic NN Hessian**
1687
+ behind a self-verifying switch — the first in-domain frequency item of a run
1688
+ computes both routes and compares (closed-shell probe first; 108 of the 720
1689
+ frequency structures are open shell and fall back PER STRUCTURE, not per
1690
+ run); **Huber loss** (`--huber-delta`, default 10 in sqrt-normalized-loss
1691
+ units) bounds one sample's gradient influence without a cliff, replacing the
1692
+ hard per-item cap that false-positived on a legitimate hard sample at 1.86e2
1693
+ (`--max-sample-loss` remains, disabled, as a loud diagnostic; non-finite item
1694
+ losses are always dropped); the physical parameter floors (Slater, GAM, REPA)
1695
+ are now **clamps** mirroring the head outputs' own `ClampSte` bound — counted
1696
+ and announced via `overlay_clamp_events()`, never silent — instead of hard
1697
+ errors that killed a whole structure over one shell; a structure whose SCC
1698
+ ladder fails **3 consecutive epochs is quarantined** for the rest of the run
1699
+ by name (one diverging reference had cost 304 s per epoch, every epoch);
1700
+ `--max-items` draws a deterministic stratified subsample (~3:3:1 ratio,
1701
+ element coverage first) for bounded runs; and the epoch line reports
1702
+ `SKIPPED` / `OUTLIER` / `QUARANTINED` / `PCLAMP` counters so a run that is
1703
+ quietly training on less than it thinks is visible at a glance.
1704
+ - **Dataset.** 106 structures / 46 reactions -> 555 / 264 -> **6391 / 2227**
1705
+ (the bulk expansion to the mandated >= 5000 items at
1706
+ energy : force : frequency ~ 3 : 3 : 1): 47 further GMTKN55 subsets, the
1707
+ S66x8/S22x5/NBC10/HBC6 dissociation curves, the W4-17 extension, the
1708
+ remaining MOR41 reactions, the BOTNet force families grown to **2150
1709
+ structures with reference force vectors**, and **720 harmonic-frequency
1710
+ structures** (292 CCSD(T)/cc-pVTZ + 412 B3LYP/6-31G* CCCBDB cohorts with
1711
+ same-level geometries + the 16 legacy records). Every record carries
1712
+ provenance; every reaction re-balances through the loader; all counts are
1713
+ pinned by tests. Trainer support for the scale: `--batch-size` concurrent
1714
+ batching and `--freq-per-epoch` deterministic frequency-item rotation.
1715
+ - **Deployment.** An out-of-domain **fallback** rejects the learned correction
1716
+ and runs plain GFN2 (`--nn-fallback`), and **parameter distillation**
1717
+ (`nn_eval --distill-params`) projects a trained model onto a static GFN2
1718
+ parameter file — recovering 40-57% of the reaction-energy gain but degrading
1719
+ forces below stock GFN2, which is reported rather than smoothed over.
1720
+
1721
+ ### Interfaces, packaging and build
1722
+
1723
+ - **NN-xTB reaches ASE.** `GFN2RSCalculator(nn_weights="model.gfn2nn")` puts
1724
+ the learned surface behind the ordinary ASE properties, so an optimizer or an
1725
+ MD run needs no other change; the forces are exact for that surface (the
1726
+ correction's own geometry dependence is differentiated, not frozen), so a
1727
+ relaxation converges to a real stationary point of it. `nn_fallback` arms the
1728
+ out-of-domain guard and its verdict is reported in `results` alongside the
1729
+ plain-GFN2 baseline energy and the head saturation.
1730
+
1731
+ What it does NOT cover, it now refuses. `get_tda`, `get_td_gradient`,
1732
+ `get_hessian` and `export_molden` run through `Gfn2NativeCalculator`, which
1733
+ never sees the overlay, so with `nn_weights` set they raise instead of
1734
+ handing back GFN2 derivatives for an NN-xTB energy - a mismatch between an
1735
+ energy surface and its derivatives is far harder to notice than an error.
1736
+ Periodic input, stress, and `spin_polarized`/`dc`/`experimental_corrections`
1737
+ raise for the same reason. `tests/test_python_api.py` pins both halves: that
1738
+ every guard fires before the weight file is even opened, and that a
1739
+ calculator without `nn_weights` is completely unaffected.
1740
+
1741
+ - **Freely specifiable NN-xTB training data**: `--dataset` (both `nn_train` and
1742
+ `nn_eval`) is now **repeatable** and accepts a `.gfn2ds` file or a directory
1743
+ of them in any mix, all loading into one dataset with one structure-name
1744
+ namespace (`Dataset::load_paths`; `Dataset::load_dir` unchanged).
1745
+ - **Deterministic batched NN-xTB training** (`--batch-size`,
1746
+ `TrainConfig::batch_size`): the items of a batch are evaluated concurrently
1747
+ and their MEAN gradient taken as one optimizer step, with results reduced in
1748
+ item order — bit-reproducible for any batch size, and `1` (the default)
1749
+ reproduces the historical per-item stepping exactly. The read-only passes
1750
+ (pre-normalization, holdout metrics) are parallelized the same way. Measured
1751
+ on the bundled S66 subset (tiny preset, 2 epochs end to end): 92.7 s at
1752
+ batch 1 vs 30.3 s at batch 8. This is the throughput path toward the
1753
+ 100k-item training target.
1754
+ - **Training learning-rate floor**: `min_lr` now defaults to `1e-5` and floors
1755
+ EVERY reduction (plateau scheduler and saturation backoffs — the plateau
1756
+ path previously ignored it); the per-item saturation backoff is rate-limited
1757
+ by the same cooldown as the epoch-end guard. Together these close the
1758
+ failure mode where one hard-saturating structure halved the learning rate
1759
+ every epoch until the run could no longer move (measured collapse to 1e-9).
1760
+ - CLI/Python/ASE wiring for the SCF accelerator, Hessian mode, Ln-xTB, DC,
1761
+ k-point TRS folding, Molden export and the NN paths; three console scripts
1762
+ (`gfn2-rs`, `gfn2-nn-train`, `gfn2-nn-eval`); ASE energy semantics unified on
1763
+ the Mermin energy GFN2-xTB actually reports.
1764
+ - Wavefunction export in Molden format (verified by reconstructing the overlap
1765
+ matrix from the written file alone to 2e-13).
1766
+
1767
+ ### Fixed
1768
+
1769
+ - **Two eV -> Hartree constants** were in use: the SCF converted the CN
1770
+ self-energy shift with the CODATA value while every derivative path used the
1771
+ legacy xtb constant GFN2 was parametrized against, so the derivatives were
1772
+ differentiating a slightly different H0 than the SCF built.
1773
+ - **Radii-table unit conversion** used CODATA where xtb uses the legacy Bohr
1774
+ radius (~1e-7 offset in coordination numbers and the CN-dependent H0).
1775
+ - **Five analytic-Hessian defects** that partially cancelled: a missing CN chain
1776
+ rule in the CPHF right-hand side, a half-weight mixed CN cross term, an
1777
+ inverted AES charge-quadrupole sign, a wrongly-dropped frozen-multipole AES
1778
+ block, and a spurious factor of 2 in the Bloch CN response fold.
1779
+ - **The CN cross term was screened on the wrong quantity.** An `|S| <= 1e-18`
1780
+ gate also discarded a term proportional to the overlap GRADIENT, which is
1781
+ ordinary for an s-p pair whose overlap vanishes by symmetry: 3.3e-6 Eh/bohr^2
1782
+ on a water dimer, now 1.19e-8 (the FD floor).
1783
+ - **The learnable radii were read from the frozen tables in six derivative
1784
+ walks.** A per-atom radius reaches the physics only through the
1785
+ `Gfn2Parameters` accessors (`poly_radius_for_atom`, `cn_radius_for_atom`,
1786
+ ...); six places called `data_tables::atomic_radius_bohr` /
1787
+ `covalent_radius_d3_bohr` directly instead, so with a non-zero radius shift
1788
+ they differentiated a slightly different H0 than the energy built. The
1789
+ visible symptom was in the ANALYTIC `dE/dp` of eight *other* head classes:
1790
+ the frozen-orbital head gradients disagreed with the fully re-converged SCF
1791
+ finite difference by 0.01-1% for `atom.en`, `shell.lev`, `shell.kcn`,
1792
+ `shell.poly`, `shell.exp`, `global.ks`, `global.kp` and `global.enscale`,
1793
+ while the radius heads themselves looked correct (they take the frozen-FD
1794
+ route, which recomputes everything consistently). Fixed in
1795
+ `nn::param_grad`, `native_gradient::shell_poly_factor_derivative`,
1796
+ `nn::hessian_analytic`, `nn::hessian_exp_family`,
1797
+ `third_derivatives::response3` and `third_derivatives::h0_pref3` (new
1798
+ `cn_third_derivatives_with_radii`); the DFT-D4 covalent CN keeps its own
1799
+ frozen table by design. All 41 variational probes now agree.
1800
+ The force loss reaches the four radius heads only through the double finite
1801
+ difference (the analytic force adjoint's documented coverage gap), and that
1802
+ composition had no test at all - it now has one.
1803
+ - **The r^-3 Klopman-Ohno Ewald tail** gained its regularized `G = 0` term; that
1804
+ kernel is now independent of the Ewald splitting parameter (see Known issues
1805
+ for what remains).
1806
+ - **The reciprocal-space AES charge-quadrupole potential missed the
1807
+ Poisson-summation sign.** The Ewald AES energy stores
1808
+ `AES_RECIP_QTHETA_SIGN * AES_QTHETA_COEFF = -1/3` in its structure factor,
1809
+ but the conjugate quadrupole potential block still used `+1/3`, making the
1810
+ reciprocal quadrupole potential the negative of the variational derivative
1811
+ of the energy (~1e-4 on periodic water; it also broke the Hellmann-Feynman
1812
+ property the divide-and-conquer periodic gradients rely on). The analytic
1813
+ potential-derivative mirror now reads the production constants directly so
1814
+ the two can no longer drift apart.
1815
+ - **The streamed D4 ATM group builder duplicated groups across workers.** The
1816
+ parallel three-body build splits the flattened (center, neighbor) work list
1817
+ mid-center, and each worker emitted its own partial `(i, j, k)` groups; in a
1818
+ small cell with many images the duplication scaled with the worker count and
1819
+ broke the `<= nat^3` distinct-group bound the cache gate had been promised
1820
+ (644 groups for a 6-atom cell). The join now merges partials by key.
1821
+ - **The training saturation guard could not do its job**: it answered a state
1822
+ variable with a rate control and drove the learning rate to zero without
1823
+ reducing saturation (measured: 0.912 pinned while the LR fell 1e-4 -> 6e-9).
1824
+ It is now rate-limited and fires only while saturation is rising.
1825
+ - **The ASE calculator ignored the atoms it was attached to.** `atoms.calc =
1826
+ GFN2RSCalculator(...)` followed by `get_tda`, `get_td_gradient`, `get_hessian`
1827
+ or `export_molden` with no atoms argument — the form the docs show — raised
1828
+ `RuntimeError: no ASE Atoms object supplied or attached` unless a single point
1829
+ had happened to run first, because ASE attaches through a `set_atoms` hook the
1830
+ base `Calculator` does not define. The attachment is now kept live and
1831
+ separate from `self.atoms` (ASE's copy of the last calculated geometry), so a
1832
+ geometry moved in place is picked up rather than silently reused.
1833
+ - Numerous smaller wiring fixes: ASE `scf_accelerator` forwarding, Python
1834
+ `mode_used`, `nn_eval` SCF-convergence enforcement, DC front-end defaults.
1835
+ - **The open-shell analytic NN Hessian is now exact** instead of guarded off.
1836
+ The Eq. 25 correction blocks' two response solves previously used the
1837
+ closed-shell CPHF space, which silently dropped the (doubly -> singly
1838
+ occupied) beta rotations — up to 25% of the correction on CH while H2O+ was
1839
+ accidentally right. Both solves now run the coupled two-channel spin CPHF
1840
+ (geometric totals plus the new relaxed spin parameter CPHF). Accepted at
1841
+ 2.0e-12 (CH2 triplet) / 6.3e-12 (H2O+) against the Richardson-extrapolated
1842
+ semi-numerical reference, and the spin machinery pushed through a
1843
+ closed-shell reference reproduces the closed-shell correction blocks to
1844
+ 9e-14. Fractional (smeared) channel occupations still error loudly and fall
1845
+ back per structure in the trainer.
1846
+
1847
+ ### Performance
1848
+
1849
+
1850
+ - **Two scaling-order reductions in the Hessian assembly.** Both replace a
1851
+ quantity recomputed inside a doubly-nested `ndim x ndim` loop with one
1852
+ computed once, so the cost falls by a factor of the system size rather than
1853
+ by a constant.
1854
+ - `nn::hessian_analytic`: the frozen theta-theta block recomputed four
1855
+ loop-invariant shell sums for every `(a, b)` pair. One of them, the
1856
+ third-order `q^T G q` contraction, is a full `nshell^2` double sum with no
1857
+ dependence on either loop index at all, which made an `O(N^2)` block cost
1858
+ `O(N^4)`. All four are now hoisted; two become `nshell`-length vectors,
1859
+ one an `ndim x nshell` table, one a scalar.
1860
+ - `hessian::direct`: the k-point AES frozen block ran
1861
+ `aes_multipole_cross_energy` - three Ewald sums - once per matrix element,
1862
+ `3 ndim^2` Ewald evaluations in total. The AES energy is a quadratic form
1863
+ in the moments and its potentials are that form applied to a moment set,
1864
+ so `E(a+b) - E(a) - E(b) = a . (T b)`: the block now builds `ndim`
1865
+ potential sets and contracts, `O(N^3)` instead of `O(N^4)`.
1866
+ - The contraction has one trap worth recording, because the first two
1867
+ attempts at it were wrong. `AesPotentials::finish` pushes the quadrupoles
1868
+ through `gfn2_traceless`, so the potentials are `dE/dM_raw` - gradients in
1869
+ the ten-component RAW AO moment basis - while callers hold projected CAMM
1870
+ moments. Pairing them directly is off by the `3/2` that projection applies
1871
+ on the quadrupole slots, and against the Ewald reference that reads as a
1872
+ plausible "off by about two", which invites fitting a constant instead of
1873
+ finding the basis mismatch. The fix contracts the traceless pre-image
1874
+ `(2/3) theta` instead. `hessian::tests::aes_potential_contract` now pins
1875
+ each premise separately - the energy is homogeneous of degree two and
1876
+ satisfies Euler's theorem, the potential map is linear, every one of the
1877
+ ten slots matches a finite difference of the energy with respect to the
1878
+ raw component (this is the test that *names* the basis), and
1879
+ `gfn2_traceless` is self-adjoint with `(2/3) theta` as its pre-image - so
1880
+ a future change cannot re-introduce a convention error that only an
1881
+ aggregate comparison would have to catch.
1882
+ - **Concurrent evaluation no longer spawns OS threads per structure, and no
1883
+ longer nests its own parallelism.** The library parallelizes at two levels:
1884
+ inside one calculation (AO pair blocks, Pulay walkers, CPHF columns, D4
1885
+ triples) and — for callers with many independent calculations, above all the
1886
+ NN trainer — across whole structures. Two defects made the second level
1887
+ fight the first. `electronic::scc_common::precompute_ao_image_integral_pairs`
1888
+ used `std::thread::scope`, spawning up to 16 fresh OS threads *per
1889
+ structure* (the gate trips at 64 directed pairs, so at roughly eight atoms);
1890
+ thread creation and teardown take process-wide locks in the OS, so
1891
+ concurrent callers serialized on them, and four workers meant 64 threads
1892
+ competing for 8 physical cores. All 25 such sites across the crate - the
1893
+ integral pre-pass, both Pulay walkers, the Gamma and Bloch CPHF right-hand
1894
+ sides and solves, the CPHF and finite-difference Hessian column loops, the
1895
+ k-point SCF map, and the four D4 pair/ATM evaluators - now use rayon's
1896
+ persistent pool through the shared `defaults::map_chunks` /
1897
+ `map_index_chunks` helpers. The chunking is unchanged and rayon's `collect`
1898
+ preserves input order, so the floating-point summation order - and every
1899
+ result - is bit-identical; the index tags and sorts the scoped version
1900
+ needed to restore order are simply gone.
1901
+ Separately, `defaults::run_inner_sections_serially` makes an inner parallel
1902
+ section run serially when the caller is already on a rayon worker: nesting
1903
+ adds no parallelism, since both levels draw on one pool. A calculation
1904
+ driven from the CLI or the Python bindings is not on a pool thread and keeps
1905
+ the inner parallelism it always had. Measured on the trainer's
1906
+ pre-normalization pass (full dataset, four threads): the frequency phase
1907
+ fell from 869.5 s to 372.9 s. `native_gradient::PULAY_FORCE_SERIAL` was a
1908
+ hand-rolled version of the same idea for one call site; it still applies and
1909
+ is now the special case of a general rule.
1910
+ - **Process-global locks are out of the per-SCC-iteration path.** `std::env::var`
1911
+ takes a process-wide lock (a critical section around the environment block
1912
+ on Windows) and allocates; `fourth_order_coefficient` read `GFN2_FOURTH_ORDER_C`
1913
+ on every call from the third-order energy and potential, and the AES
1914
+ component masks and D4 thread controls did likewise. The D4 pair/ATM
1915
+ geometry memos and the C6 pair tables were `Mutex<HashMap>`s consulted per
1916
+ structure — and with a shared eight-entry cap, concurrent workers holding
1917
+ different structures evicted each other's entries and then paid the lock to
1918
+ rebuild them. All are now read once per thread (`defaults::cached_env`,
1919
+ thread-local memos in front of the shared C6 table), and
1920
+ `available_parallelism` — a syscall — is cached rather than called per
1921
+ calculation. The lattice image-offset and reciprocal-vector lists got the
1922
+ same treatment.
1923
+ - **The trainer's SCC-iteration cost was dominated by measurement distortion
1924
+ and lock convoys, both gone.** `profile::event` printed a line per call from
1925
+ inside the per-iteration D4 scope, serializing every rayon worker on the
1926
+ stderr lock and billing the wait to the enclosing scope (the "98.6% of SCC
1927
+ is D4" reading); events now aggregate into the profile report. The real
1928
+ per-iteration waste — covCN re-derivation, two geometry-cache mutexes and
1929
+ nat(nat+1)/2 C6-table mutex acquisitions per iteration, plus per-call OS
1930
+ thread spawns in the pair/ATM evaluators — is hoisted into a per-structure
1931
+ `D4ScfContext` built once before each SCC loop (Gamma, spin-polarized and
1932
+ k-point paths), bit-identically. Measured on the profiled trainer slice:
1933
+ the SCC iteration mean fell 93.1 -> 32.7 ms and the D4 share 91.7 -> 2.2 ms.
1934
+ - **The NN gradient no longer re-runs the SCC.** The evaluation's converged
1935
+ state is fed straight into the gradient assembly
1936
+ (`native_analytic_gradient_with_electronic`) whenever its tolerance meets
1937
+ the gradient's 1e-10 stationarity cap; the trainer now evaluates at that
1938
+ tolerance by construction, so one gradient-grade SCC per item replaces the
1939
+ historical pair (~9 s of re-converged SCC per training gradient, and half
1940
+ the SCCs of every semi-numerical NN Hessian column).
1941
+ - **The fixed-parameter analytic Hessian is 5.0x faster at 24 atoms**
1942
+ (11.03 s -> 2.22 s; scaling exponent 3.38 -> ~2.9): three per-column AO
1943
+ walks rebuilt every moment-integral derivative for every column and were 95%
1944
+ of the assembly; they are now precomputed once. The `O(nocc^2 nao^2)` metric
1945
+ folds became matmuls (same sums re-associated; fingerprints agree to 15-17
1946
+ significant digits).
1947
+ - **The NN analytic Hessian reuses the fixed-p Hessian's own Gamma CPHF
1948
+ response** for its geometric context instead of re-solving the identical
1949
+ 3N-column system (~25-30% of the assembly).
1950
+ - **The force-loss adjoint has an exact analytic route** replacing the double
1951
+ finite difference, verified per family to ~5e-11 relative (Richardson) with
1952
+ a rigid-translation pin. Measured today it is 7.5-12.6x SLOWER than the
1953
+ double FD (the mixed second derivative needs Hessian-cost objects), so a
1954
+ self-verifying switch computes both once per run, checks agreement, times
1955
+ them and keeps the faster route — the moment the analytic route's cost work
1956
+ lands it flips on its own, with no config change.
1957
+
1958
+ - **The periodic total energy still depends on the Ewald splitting parameter.**
1959
+ After the `r^-3` `G = 0` fix the residual drift is 8.1e-3 Eh in the isotropic
1960
+ SCC channel and 7.7e-4 Eh in AES (water in a 14 bohr cube, alpha 0.16..0.32).
1961
+ `tests/physics.rs::ewald_splitting_parameter_independence` is `#[ignore]`d and
1962
+ documents it; relative energies at fixed cell and fixed Ewald settings are
1963
+ unaffected.
1964
+ - **Periodic third derivatives are out of scope by design** (not deferred), as
1965
+ are k-point NN-xTB gradients and DC gradients under a k-point mesh or strain.
1966
+ - **DC is not linear scaling overall.** It removes the cubic eigensolve
1967
+ (`lowdin_solve` N^2.95 -> `dc_density_step` N^1.59, 4.93x faster at 768
1968
+ atoms with `--no-d4-atm`), but H/S construction, the SCC kernel, AES and
1969
+ the exact Ewald electrostatics remain full-system on every iteration, so
1970
+ total wall time scales N^1.68 vs N^2.64. With D4 ATM on (the default) the
1971
+ streamed three-body term dominates the DC run (73% at 768 atoms, ~N^2.9)
1972
+ and the gap narrows to N^2.39 vs N^2.43 (1.92x at 768). At 1536 atoms the
1973
+ per-SCC-iteration cost holds N^1.33; the N^2.37 total is inflated by SCC
1974
+ convergence (43 iterations vs 19 at 768). See `docs/measurements.md`.
1975
+ - **The NN fallback does not detect a confidently-wrong model.** On reaction
1976
+ families the shipped model never saw, the network was 4x worse than plain
1977
+ GFN2 and the fallback fired on 0 of 555 structures. Its triggers are
1978
+ out-of-domain proxies; `used_fallback == false` is not a quality guarantee.
1979
+ - Everything NN-xTB, DC, Ln-xTB and third-derivative related is marked
1980
+ experimental and may change.
1981
+ ### Known issues
1982
+
1983
+ - **The EEQ-BC analytic Hessian carries a ~5e-6 residual.** With
1984
+ `d4_eeqbc = true` AND D4 on, the relaxed analytic Hessian differs from a
1985
+ finite difference of its own gradient by 3.8e-6 (water) / 5.1e-6 (dimer)
1986
+ Eh/bohr^2, where every other charge source reaches ~5e-9. The pieces that
1987
+ could plausibly explain it have each been proven exact instead: the EEQ-BC
1988
+ charge curvature to 5.7e-13, the whole dispersion side (fixed-charge block
1989
+ plus all three chain blocks) to 6.2e-16 against the FD of the exact D4 force,
1990
+ and that force bit-identical (3.6e-18) to what `native_analytic_gradient`
1991
+ adds for this charge source. It survives every combination of the four
1992
+ SCC/D4 decoupling flags, is not a symmetrization artefact, and reproduces
1993
+ with no finite difference at all as `H(D4 on) - H(D4 off) != [fixed block +
1994
+ chain]`. Gated at 1e-5 in `eeqbc_d4_analytic_relaxed_hessian_matches_
1995
+ gradient_fd_*` at the MEASURED floor, to be tightened to 1e-6 when closed.
1996
+ `hessian_mode = "fd"` is the escape hatch and is still honoured for this
1997
+ charge source.
1998
+ - ~~**The periodic total energy still drifts with the Ewald splitting
1999
+ parameter.**~~ **Not an open issue — withdrawn after measurement.** This
2000
+ entry was written from the `#[ignore]` reason on
2001
+ `physics::ewald_splitting_parameter_independence`, which described the state
2002
+ at some earlier point in v0.5.0 development and had gone stale while the test
2003
+ stayed disabled. Re-run, the test **passes**: sweeping alpha over
2004
+ 0.16 .. 0.32 bohr^-1 on water in a 14 bohr cube with both series converged,
2005
+ the SCC total energy agrees to **2.7e-15 Eh** (5.3e-16 relative, reproducible
2006
+ exactly across runs) — machine precision, not the 8.1e-3 Eh the stale note
2007
+ claimed. The regularized `G = 0` term for the `r^-3` Klopman-Ohno tail
2008
+ (`ko_ewald::r3_ewald_g0_e1`) closed it. The test is now **enabled**, so the
2009
+ invariance is a standing regression guard over the whole periodic
2010
+ electrostatic stack rather than a disabled note. See the Fixed section.
2011
+
2012
+ - **`mindless03` no longer reproduces its xtb oracle reference.** That system
2013
+ has several self-consistent SCC solutions and the reference is a *metastable*
2014
+ one: xtb generated it at `acc = 1.0` (charge convergence 1e-4 e), stopping
2015
+ before the iteration left that basin. Converging to 1e-9 lands 6.8e-2 Eh
2016
+ ABOVE the reference; raising the iteration budget to 2000 lands 4.4 Eh BELOW
2017
+ it; converging first at xtb's own 1e-4 and warm-starting the tight solve
2018
+ reproduces the 6.8e-2 result exactly, so this iteration does not pass through
2019
+ the reference basin at all. A binary built before the NN-xTB radius-class work
2020
+ passes the case, so basin selection changed during v0.5.0 development - but
2021
+ the energy functional is not implicated: the other six oracle sets still pass
2022
+ at their own tight floors (water agrees to 1e-11..1e-13 on every component)
2023
+ and the analytic gradient stays FD-exact to 3e-10. Kept visible as the
2024
+ `#[ignore]`d `oracle_mindless03_metastable_reference`, with the full
2025
+ measurement record in its doc comment.
2026
+ - **The Mermin `dF/dT = -S` identity is not verified on the spin-polarized
2027
+ path.** The O2 triplet fixture does not converge in 3000 iterations at the
2028
+ test's settings - the spin driver still uses the legacy charge-space
2029
+ accelerator, since ADIIS -> CDIIS was extended to the Gamma and Bloch drivers
2030
+ only. With a decaying level shift it converges, but the identity then holds
2031
+ to just 6.4e-4 with the raw differences plateauing rather than falling as
2032
+ `h^2`, which is the signature of converging to a fixed point of the shifted
2033
+ Hamiltonian. The entropy bookkeeping itself is correct (the spin path sums
2034
+ the alpha and beta channel entropies separately). Closed-shell Gamma and the
2035
+ metallic k-point path verify to 1.3e-8 and 4.1e-13 respectively. Kept as the
2036
+ `#[ignore]`d `..._spin_polarized` test.
2037
+
2038
+
2039
+ ## [0.4.5]
2040
+ - No release notes were recorded in-tree for this version.
2041
+
2042
+ ## [0.4.4]
2043
+ - Shipped the MOR41-fit uniform-optimum `experimental_corrections` preset
2044
+ (`range_separation.rs`; `--experimental-corrections`).
2045
+
2046
+ ## [0.4.3]
2047
+ - DIIS error vector switched to the orthonormalised commutator
2048
+ `X^T (FPS - SPF) X`; a 22-atom stall case dropped from 2129 to 92
2049
+ iterations.
2050
+
2051
+ ## [0.4.2]
2052
+ - LC SCF robustness: damped-mixing DIIS fallback, DIIS restart on residual
2053
+ growth, automatic virtual level shift (fixes CO).
2054
+
2055
+ ## [0.4.1]
2056
+ - EEQ-BC CG indefinite-block fix (H2/I2 homonuclear diatomics) via LU
2057
+ fallback on the bordered KKT system.
2058
+
2059
+ ## [0.4.0]
2060
+ - Finite electronic temperature (Mermin free energy) for the OFX/LC path;
2061
+ `param_deriv` experimental option-knob finite-difference targets.
2062
+
2063
+ ## [0.3.0]
2064
+ - Range-separation scope bound `OMEGA_MAX_SUPPORTED = 1.0`.