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.
- gfn2_rs_python-0.6.1/CHANGELOG.md +2064 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/Cargo.lock +1 -1
- gfn2_rs_python-0.6.1/Cargo.toml +142 -0
- gfn2_rs_python-0.6.1/LICENSES/Apache-2.0.txt +202 -0
- gfn2_rs_python-0.6.1/LICENSES/BSD-3-Clause.txt +11 -0
- gfn2_rs_python-0.6.1/LICENSES/CC-BY-4.0.txt +156 -0
- gfn2_rs_python-0.6.1/LICENSES/GPL-3.0-or-later.txt +674 -0
- gfn2_rs_python-0.6.1/LICENSES/LGPL-3.0-or-later.txt +165 -0
- gfn2_rs_python-0.6.1/LICENSES/LLVM-exception.txt +15 -0
- gfn2_rs_python-0.6.1/LICENSES/MIT.txt +18 -0
- gfn2_rs_python-0.6.1/LICENSES/MPL-2.0.txt +373 -0
- gfn2_rs_python-0.6.1/LICENSES/Unicode-3.0.txt +39 -0
- gfn2_rs_python-0.6.1/LICENSES/Zlib.txt +11 -0
- gfn2_rs_python-0.6.1/NOTICE +188 -0
- gfn2_rs_python-0.6.1/PKG-INFO +514 -0
- gfn2_rs_python-0.6.1/README.md +466 -0
- gfn2_rs_python-0.6.1/THIRD_PARTY_LICENSES_RUST.md +12302 -0
- gfn2_rs_python-0.6.1/THIRD_PARTY_NOTICES.md +470 -0
- gfn2_rs_python-0.6.1/data/param_gfn2-xtb.txt +1601 -0
- gfn2_rs_python-0.6.1/data/param_spin_gfn2.txt +161 -0
- gfn2_rs_python-0.6.1/docs/README.md +42 -0
- gfn2_rs_python-0.6.1/docs/measurements.md +1152 -0
- gfn2_rs_python-0.6.1/docs/nn-xtb.md +1094 -0
- gfn2_rs_python-0.6.1/docs/pip_reachability.md +245 -0
- gfn2_rs_python-0.6.1/docs/python-api.md +948 -0
- gfn2_rs_python-0.6.1/docs/rust-api.md +1284 -0
- gfn2_rs_python-0.6.1/docs/whats-new-0.6.1.md +491 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/ase_singlepoint.py +2 -2
- gfn2_rs_python-0.6.1/examples/nn_bench.rs +302 -0
- gfn2_rs_python-0.6.1/models/README.md +312 -0
- gfn2_rs_python-0.6.1/pyproject.toml +120 -0
- gfn2_rs_python-0.6.1/python/gfn2_rs/__init__.py +152 -0
- gfn2_rs_python-0.6.1/python/gfn2_rs/_cli.py +27 -0
- gfn2_rs_python-0.6.1/python/gfn2_rs/ase.py +889 -0
- gfn2_rs_python-0.6.1/python/gfn2_rs/models/README.md +41 -0
- gfn2_rs_python-0.6.1/python/gfn2_rs/models/nnxtb-v0.6.1-provisional.gfn2nn +0 -0
- gfn2_rs_python-0.6.1/scripts/collect_rust_licenses.py +265 -0
- gfn2_rs_python-0.6.1/scripts/extract_lnxtb_params.py +361 -0
- gfn2_rs_python-0.6.1/scripts/make_release_zip.py +245 -0
- gfn2_rs_python-0.6.1/scripts/make_tblite_oracle.py +546 -0
- gfn2_rs_python-0.6.1/scripts/rmd17_relative_energies.py +227 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/aes.rs +355 -101
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/basis.rs +42 -5
- gfn2_rs_python-0.6.1/src/bin/cubic_check.rs +1051 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/gfn2_check_elements.rs +6 -7
- gfn2_rs_python-0.6.1/src/bin/gfn2_rs.rs +9 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/gradient_check.rs +8 -8
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/hessian_check.rs +85 -7
- gfn2_rs_python-0.6.1/src/bin/nn_eval.rs +10 -0
- gfn2_rs_python-0.6.1/src/bin/nn_train.rs +10 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/param_deriv.rs +22 -9
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/stress_check.rs +8 -8
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/bin/water_cluster_opt.rs +6 -7
- gfn2_rs_python-0.6.1/src/cli/main_cli.rs +4735 -0
- gfn2_rs_python-0.6.1/src/cli/mod.rs +12 -0
- gfn2_rs_python-0.6.1/src/cli/nn_eval_cli.rs +1639 -0
- gfn2_rs_python-0.6.1/src/cli/nn_train_cli.rs +1052 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/coordination.rs +277 -6
- gfn2_rs_python-0.6.1/src/cphf/bloch.rs +3347 -0
- gfn2_rs_python-0.6.1/src/cphf/gamma.rs +1604 -0
- gfn2_rs_python-0.6.1/src/cphf/mod.rs +1153 -0
- gfn2_rs_python-0.6.1/src/cphf/rhs_gamma.rs +1000 -0
- gfn2_rs_python-0.6.1/src/cphf/solver.rs +944 -0
- gfn2_rs_python-0.6.1/src/cphf/spin_bloch.rs +1114 -0
- gfn2_rs_python-0.6.1/src/cphf/spin_gamma.rs +1617 -0
- gfn2_rs_python-0.6.1/src/cphf/tests.rs +3854 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/d4_reference.rs +425 -5
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/data_tables.rs +12 -3
- gfn2_rs_python-0.6.1/src/dc.rs +2847 -0
- gfn2_rs_python-0.6.1/src/defaults.rs +217 -0
- gfn2_rs_python-0.6.1/src/dfpt/bands.rs +378 -0
- gfn2_rs_python-0.6.1/src/dfpt/cn.rs +135 -0
- gfn2_rs_python-0.6.1/src/dfpt/complex.rs +306 -0
- gfn2_rs_python-0.6.1/src/dfpt/kernel.rs +485 -0
- gfn2_rs_python-0.6.1/src/dfpt/perturbation.rs +370 -0
- gfn2_rs_python-0.6.1/src/dfpt/q_tests.rs +1274 -0
- gfn2_rs_python-0.6.1/src/dfpt/response.rs +346 -0
- gfn2_rs_python-0.6.1/src/dfpt/skeleton.rs +478 -0
- gfn2_rs_python-0.6.1/src/dfpt/solver.rs +335 -0
- gfn2_rs_python-0.6.1/src/dfpt.rs +1709 -0
- gfn2_rs_python-0.6.1/src/dispersion.rs +3795 -0
- gfn2_rs_python-0.6.1/src/eeqbc.rs +1339 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/eeqbc_mctc_data.rs +21 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/eeqbc_params.rs +22 -0
- gfn2_rs_python-0.6.1/src/electronic/bloch.rs +3761 -0
- gfn2_rs_python-0.6.1/src/electronic/frontier.rs +617 -0
- gfn2_rs_python-0.6.1/src/electronic/gamma.rs +1068 -0
- gfn2_rs_python-0.6.1/src/electronic/lc.rs +877 -0
- gfn2_rs_python-0.6.1/src/electronic/mod.rs +782 -0
- gfn2_rs_python-0.6.1/src/electronic/scc_common.rs +3181 -0
- gfn2_rs_python-0.6.1/src/electronic/scc_strategy.rs +1084 -0
- gfn2_rs_python-0.6.1/src/electronic/spinpol.rs +1394 -0
- gfn2_rs_python-0.6.1/src/electronic/tests.rs +773 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/gradients.rs +36 -14
- gfn2_rs_python-0.6.1/src/hessian/aes.rs +677 -0
- gfn2_rs_python-0.6.1/src/hessian/aes_potential_deriv.rs +1001 -0
- gfn2_rs_python-0.6.1/src/hessian/classical.rs +543 -0
- gfn2_rs_python-0.6.1/src/hessian/cphf_bloch.rs +618 -0
- gfn2_rs_python-0.6.1/src/hessian/cphf_gamma.rs +797 -0
- gfn2_rs_python-0.6.1/src/hessian/d4.rs +1565 -0
- gfn2_rs_python-0.6.1/src/hessian/d4_potential_deriv.rs +439 -0
- gfn2_rs_python-0.6.1/src/hessian/mod.rs +1809 -0
- gfn2_rs_python-0.6.1/src/hessian/perf.rs +330 -0
- gfn2_rs_python-0.6.1/src/hessian/response.rs +1758 -0
- gfn2_rs_python-0.6.1/src/hessian/scc_shell.rs +646 -0
- gfn2_rs_python-0.6.1/src/hessian/spin_response.rs +690 -0
- gfn2_rs_python-0.6.1/src/hessian/tests.rs +9260 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/hessian_pulay.rs +177 -166
- gfn2_rs_python-0.6.1/src/integrals.rs +1690 -0
- gfn2_rs_python-0.6.1/src/integrals_third_derivatives.rs +818 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/ko_ewald.rs +927 -50
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/kpoints.rs +209 -51
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/lattice.rs +114 -32
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/lib.rs +61 -13
- gfn2_rs_python-0.6.1/src/licenses.rs +128 -0
- gfn2_rs_python-0.6.1/src/linalg.rs +557 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/model.rs +72 -15
- gfn2_rs_python-0.6.1/src/molden.rs +1126 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/multipole.rs +142 -15
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/native_gradient.rs +745 -262
- gfn2_rs_python-0.6.1/src/nn/calculator.rs +2112 -0
- gfn2_rs_python-0.6.1/src/nn/cg.rs +415 -0
- gfn2_rs_python-0.6.1/src/nn/dataset.rs +2167 -0
- gfn2_rs_python-0.6.1/src/nn/distill.rs +1121 -0
- gfn2_rs_python-0.6.1/src/nn/force_adjoint.rs +1893 -0
- gfn2_rs_python-0.6.1/src/nn/freq_modes.rs +194 -0
- gfn2_rs_python-0.6.1/src/nn/heads.rs +395 -0
- gfn2_rs_python-0.6.1/src/nn/hessian_aes_family.rs +1676 -0
- gfn2_rs_python-0.6.1/src/nn/hessian_analytic.rs +4526 -0
- gfn2_rs_python-0.6.1/src/nn/hessian_d4_family.rs +1160 -0
- gfn2_rs_python-0.6.1/src/nn/hessian_exp_family.rs +2134 -0
- gfn2_rs_python-0.6.1/src/nn/mace.rs +1748 -0
- gfn2_rs_python-0.6.1/src/nn/mod.rs +39 -0
- gfn2_rs_python-0.6.1/src/nn/model_io.rs +1084 -0
- gfn2_rs_python-0.6.1/src/nn/optim.rs +453 -0
- gfn2_rs_python-0.6.1/src/nn/overlay.rs +834 -0
- gfn2_rs_python-0.6.1/src/nn/param_grad.rs +2053 -0
- gfn2_rs_python-0.6.1/src/nn/paramset.rs +147 -0
- gfn2_rs_python-0.6.1/src/nn/radial.rs +428 -0
- gfn2_rs_python-0.6.1/src/nn/rng.rs +359 -0
- gfn2_rs_python-0.6.1/src/nn/spherical.rs +660 -0
- gfn2_rs_python-0.6.1/src/nn/tape.rs +2820 -0
- gfn2_rs_python-0.6.1/src/nn/tda_gradient.rs +229 -0
- gfn2_rs_python-0.6.1/src/nn/third_derivative.rs +1349 -0
- gfn2_rs_python-0.6.1/src/nn/third_repulsion.rs +578 -0
- gfn2_rs_python-0.6.1/src/nn/train.rs +4431 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/occupations.rs +38 -0
- gfn2_rs_python-0.6.1/src/optimizer.rs +1444 -0
- gfn2_rs_python-0.6.1/src/params.rs +1695 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/profile.rs +90 -1
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/python.rs +1358 -41
- gfn2_rs_python-0.6.1/src/scc_accel.rs +620 -0
- gfn2_rs_python-0.6.1/src/spin.rs +921 -0
- gfn2_rs_python-0.6.1/src/stability.rs +342 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/system.rs +6 -0
- gfn2_rs_python-0.6.1/src/tda/davidson.rs +461 -0
- gfn2_rs_python-0.6.1/src/tda/gamma_states.rs +1010 -0
- gfn2_rs_python-0.6.1/src/tda/gradient.rs +2764 -0
- gfn2_rs_python-0.6.1/src/tda/kpoint_states.rs +1601 -0
- gfn2_rs_python-0.6.1/src/tda/mod.rs +517 -0
- gfn2_rs_python-0.6.1/src/tda/sigma.rs +116 -0
- gfn2_rs_python-0.6.1/src/tda/tests.rs +483 -0
- gfn2_rs_python-0.6.1/src/term_physics.rs +704 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/aes3.rs +761 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/aes_response3.rs +891 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/classical/tests.rs +422 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/classical.rs +1808 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/cphf3.rs +1131 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/h0_pref3.rs +883 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/jet3.rs +1061 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/mod.rs +544 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/response3/tests.rs +2204 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/response3.rs +2780 -0
- gfn2_rs_python-0.6.1/src/third_derivatives/tests.rs +491 -0
- gfn2_rs_python-0.6.1/src/vibrations.rs +1051 -0
- gfn2_rs_python-0.6.1/src/wire_sum.rs +788 -0
- gfn2_rs_python-0.6.1/tests/ORACLE_NOTES.md +364 -0
- gfn2_rs_python-0.6.1/tests/attribution.rs +462 -0
- gfn2_rs_python-0.6.1/tests/cli_matrix.rs +524 -0
- gfn2_rs_python-0.6.1/tests/common/mod.rs +192 -0
- gfn2_rs_python-0.6.1/tests/identities.rs +1652 -0
- gfn2_rs_python-0.6.1/tests/nn_freq_route_cost.rs +466 -0
- gfn2_rs_python-0.6.1/tests/oracle/tblite_data.rs +2843 -0
- gfn2_rs_python-0.6.1/tests/oracle/xtb_data.rs +1511 -0
- gfn2_rs_python-0.6.1/tests/oracle_gfn2.rs +625 -0
- gfn2_rs_python-0.6.1/tests/oracle_tblite.rs +209 -0
- gfn2_rs_python-0.6.1/tests/physics.rs +2575 -0
- gfn2_rs_python-0.6.1/tests/test_bundled_model.py +297 -0
- gfn2_rs_python-0.6.1/tests/test_cli_scope_runtime.py +437 -0
- gfn2_rs_python-0.6.1/tests/test_console_scripts.py +100 -0
- gfn2_rs_python-0.6.1/tests/test_python_api.py +795 -0
- gfn2_rs_python-0.6.1/tests/test_wheel_licenses.py +185 -0
- gfn2_rs_python-0.4.5/Cargo.toml +0 -66
- gfn2_rs_python-0.4.5/PKG-INFO +0 -193
- gfn2_rs_python-0.4.5/README.md +0 -173
- gfn2_rs_python-0.4.5/THIRD_PARTY_NOTICES.md +0 -15
- gfn2_rs_python-0.4.5/docs/python-api.md +0 -249
- gfn2_rs_python-0.4.5/docs/rust-api.md +0 -210
- gfn2_rs_python-0.4.5/pyproject.toml +0 -33
- gfn2_rs_python-0.4.5/python/gfn2_rs/__init__.py +0 -70
- gfn2_rs_python-0.4.5/python/gfn2_rs/ase.py +0 -339
- gfn2_rs_python-0.4.5/src/bin/gfn2_rs.rs +0 -2326
- gfn2_rs_python-0.4.5/src/cphf.rs +0 -8701
- gfn2_rs_python-0.4.5/src/dispersion.rs +0 -2156
- gfn2_rs_python-0.4.5/src/eeqbc.rs +0 -746
- gfn2_rs_python-0.4.5/src/electronic.rs +0 -9985
- gfn2_rs_python-0.4.5/src/hessian.rs +0 -14048
- gfn2_rs_python-0.4.5/src/integrals.rs +0 -485
- gfn2_rs_python-0.4.5/src/linalg.rs +0 -323
- gfn2_rs_python-0.4.5/src/optimizer.rs +0 -572
- gfn2_rs_python-0.4.5/src/params.rs +0 -928
- gfn2_rs_python-0.4.5/src/spin.rs +0 -380
- gfn2_rs_python-0.4.5/src/tda.rs +0 -5224
- gfn2_rs_python-0.4.5/tests/test_cli_scope_runtime.py +0 -261
- gfn2_rs_python-0.4.5/tests/test_python_api.py +0 -159
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/LICENSE +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/ferrocene.xyz +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/ferrocene_pbc_20a.xyz +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/ferrocene_pbc_24a.xyz +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2.charges +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2.multipoles +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2.xyz +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2_pbc_12a.xyz +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/h2_slab.xyz +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/spin_params_template.txt +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/water_dimer_distorted.xyz +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/examples/water_dimer_pbc_16a.xyz +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/cutoff.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/element_coverage.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/error.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/integrals_second_derivatives.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/lr_exchange.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/math.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/neighbor.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/one_center_d.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/pairlist.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/pme.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/range_separation.rs +0 -0
- {gfn2_rs_python-0.4.5 → gfn2_rs_python-0.6.1}/src/spin_projection.rs +0 -0
- {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`.
|