gfn1-rs-python 0.4.5__tar.gz → 0.4.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/Cargo.lock +1 -1
  2. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/Cargo.toml +1 -1
  3. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/PKG-INFO +1 -1
  4. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/pyproject.toml +1 -1
  5. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/dispersion.rs +65 -76
  6. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/hessian.rs +21 -114
  7. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/LICENSE +0 -0
  8. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/README.md +0 -0
  9. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/THIRD_PARTY_NOTICES.md +0 -0
  10. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/docs/python-api.md +0 -0
  11. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/docs/rust-api.md +0 -0
  12. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/docs/scope.md +0 -0
  13. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/docs/sigma_hole_preset.md +0 -0
  14. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/ammonia.xyz +0 -0
  15. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/bromoethanol.xyz +0 -0
  16. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/diamond.xyz +0 -0
  17. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/diamond222.xyz +0 -0
  18. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/dppf_pdcl2.xyz +0 -0
  19. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/h2.engrad +0 -0
  20. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/h2.xyz +0 -0
  21. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/lao_kinetic_grid_check.rs +0 -0
  22. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/mag_cpscc_check.rs +0 -0
  23. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/magnetizability_validation.rs +0 -0
  24. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/methane.xyz +0 -0
  25. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/ni_co4.xyz +0 -0
  26. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/profile_magnetic.rs +0 -0
  27. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/profile_pbc.rs +0 -0
  28. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/water.xyz +0 -0
  29. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/water100.xyz +0 -0
  30. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/water48.xyz +0 -0
  31. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/examples/water_opt.xyz +0 -0
  32. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/python/gfn1_rs/__init__.py +0 -0
  33. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/python/gfn1_rs/ase.py +0 -0
  34. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/python/gfn1_rs/native.py +0 -0
  35. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/python/gfn1_rs/torch_interop.py +0 -0
  36. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/scripts/compare_optimizations.py +0 -0
  37. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/scripts/gen_secondary_basis.py +0 -0
  38. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/scripts/optimize.py +0 -0
  39. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/scripts/run_mgo_kpoint_md.py +0 -0
  40. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/basis.rs +0 -0
  41. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/bin/gfn1_rs.rs +0 -0
  42. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/constants.rs +0 -0
  43. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/coordination.rs +0 -0
  44. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/coulomb.rs +0 -0
  45. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/cphf.rs +0 -0
  46. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/d4_reference.rs +0 -0
  47. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/data_tables.rs +0 -0
  48. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/electronic.rs +0 -0
  49. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/error.rs +0 -0
  50. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/exchange.rs +0 -0
  51. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/field.rs +0 -0
  52. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/gradient.rs +0 -0
  53. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/halogen.rs +0 -0
  54. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/hamiltonian.rs +0 -0
  55. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/integrals.rs +0 -0
  56. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/integrals_second_derivatives.rs +0 -0
  57. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/integrals_third_derivatives.rs +0 -0
  58. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/lattice.rs +0 -0
  59. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/lib.rs +0 -0
  60. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/linalg.rs +0 -0
  61. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/magnetic.rs +0 -0
  62. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/math.rs +0 -0
  63. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/model.rs +0 -0
  64. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/multipole.rs +0 -0
  65. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/nmr.rs +0 -0
  66. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/optimizer.rs +0 -0
  67. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pairlist.rs +0 -0
  68. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/param_deriv.rs +0 -0
  69. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/params.rs +0 -0
  70. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/bloch.rs +0 -0
  71. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/complex.rs +0 -0
  72. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/ewald.rs +0 -0
  73. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/ewald_multipole.rs +0 -0
  74. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/gradient.rs +0 -0
  75. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/hessian.rs +0 -0
  76. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/kpoints.rs +0 -0
  77. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/mod.rs +0 -0
  78. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/scf.rs +0 -0
  79. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/pbc/stress.rs +0 -0
  80. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/plus_u.rs +0 -0
  81. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/plus_u_dudr.rs +0 -0
  82. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/profile.rs +0 -0
  83. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/properties.rs +0 -0
  84. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/python.rs +0 -0
  85. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/repulsion.rs +0 -0
  86. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/secondary_bases/gfn1-xtb-cc-pv5z.txt +0 -0
  87. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/secondary_bases/gfn1-xtb-cc-pvdz.txt +0 -0
  88. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/secondary_bases/gfn1-xtb-cc-pvqz.txt +0 -0
  89. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/secondary_bases/gfn1-xtb-cc-pvtz.txt +0 -0
  90. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/secondary_bases.rs +0 -0
  91. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/secondary_basis.rs +0 -0
  92. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/spin.rs +0 -0
  93. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/sto.rs +0 -0
  94. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/system.rs +0 -0
  95. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/td.rs +0 -0
  96. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/third_derivative.rs +0 -0
  97. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/trah.rs +0 -0
  98. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/src/vibrational.rs +0 -0
  99. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/d4.rs +0 -0
  100. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/field.rs +0 -0
  101. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/gradient_fd_probe.rs +0 -0
  102. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/hessian.rs +0 -0
  103. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/kpoint_hessian_repro.rs +0 -0
  104. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/magnetic.rs +0 -0
  105. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/nonpbc_smoke.rs +0 -0
  106. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/optimizer.rs +0 -0
  107. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/param_deriv.rs +0 -0
  108. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/pbc_integration.rs +0 -0
  109. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/properties.rs +0 -0
  110. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/scf_accelerators.rs +0 -0
  111. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/tblite_parity.rs +0 -0
  112. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/tests/td.rs +0 -0
  113. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/CITATION.bib +0 -0
  114. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/COPYING +0 -0
  115. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/COPYING.LESSER +0 -0
  116. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/PROVENANCE.md +0 -0
  117. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/assets/parameters.toml +0 -0
  118. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/src/dftd4/data/hardness.f90 +0 -0
  119. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/src/dftd4/data/r4r2.f90 +0 -0
  120. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/src/dftd4/data/wfpair.f90 +0 -0
  121. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/src/dftd4/data/zeff.f90 +0 -0
  122. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/src/dftd4/reference.f90 +0 -0
  123. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/dftd4/src/dftd4/reference.inc +0 -0
  124. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/simple-dftd3/COPYING +0 -0
  125. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/simple-dftd3/COPYING.LESSER +0 -0
  126. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/simple-dftd3/src/dftd3/data/r4r2.f90 +0 -0
  127. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/simple-dftd3/src/dftd3/reference.f90 +0 -0
  128. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/tblite/PROVENANCE.md +0 -0
  129. {gfn1_rs_python-0.4.5 → gfn1_rs_python-0.4.6}/third_party/tblite/spin.f90 +0 -0
@@ -420,7 +420,7 @@ dependencies = [
420
420
 
421
421
  [[package]]
422
422
  name = "gfn1-rs"
423
- version = "0.4.5"
423
+ version = "0.4.6"
424
424
  dependencies = [
425
425
  "faer",
426
426
  "libm",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "gfn1-rs"
3
- version = "0.4.5"
3
+ version = "0.4.6"
4
4
  edition = "2021"
5
5
  license = "GPL-3.0-or-later"
6
6
  authors = ["ss0832"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gfn1-rs-python
3
- Version: 0.4.5
3
+ Version: 0.4.6
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: ase>=3.22 ; extra == 'ase'
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "gfn1-rs-python"
7
- version = "0.4.5"
7
+ version = "0.4.6"
8
8
  description = "Python and ASE interface for the native Rust GFN1-RS implementation"
9
9
  requires-python = ">=3.9,<3.15"
10
10
  license = "GPL-3.0-or-later"
@@ -1,8 +1,10 @@
1
1
  // SPDX-License-Identifier: GPL-3.0-or-later
2
2
  //! DFT-D3(BJ) dispersion and experimental non-PBC DFT-D4 dispersion for GFN1-xTB.
3
3
  //!
4
- //! The large D3 C6 reference table is deliberately not bundled. It is read
5
- //! from the `s-dftd3/src/dftd3/reference.f90` source used by tblite.
4
+ //! The large D3 C6 reference table is bundled under `third_party/simple-dftd3`
5
+ //! and embedded at build time (`s-dftd3/src/dftd3/reference.f90`), so it is used
6
+ //! by default; an explicit path or the `GFN1_D3_REFERENCE` environment variable
7
+ //! can override it.
6
8
  //! D4 reference data are stored with upstream provenance under `third_party/dftd4`;
7
9
  //! the GFN1 damping constants `a1`, `a2`, and `s8` are read from the
8
10
  //! user-supplied `param_gfn1-xtb.txt` through [`Gfn1Parameters`]. The experimental
@@ -21,7 +23,7 @@ use crate::params::{Gfn1Parameters, GFN1_D3_REFERENCE_ENV};
21
23
  use crate::system::PeriodicSystem;
22
24
  use std::collections::HashMap;
23
25
  use std::fs;
24
- use std::path::{Path, PathBuf};
26
+ use std::path::Path;
25
27
  use std::sync::{Arc, Mutex, OnceLock};
26
28
 
27
29
  const WF: f64 = 4.0;
@@ -1238,8 +1240,7 @@ pub fn dispersion_energy_gradient(
1238
1240
  let a1 = params.required_global("a1")?;
1239
1241
  let a2 = params.required_global("a2")?;
1240
1242
 
1241
- let reference_path = resolve_d3_reference_path(params, explicit_reference_path)?;
1242
- let reference = load_d3_reference(&reference_path)?;
1243
+ let reference = resolve_and_load_d3_reference(explicit_reference_path)?;
1243
1244
  let cn = coordination_with_derivatives(
1244
1245
  system,
1245
1246
  CoordinationOptions {
@@ -1374,8 +1375,7 @@ pub fn dispersion_energy_gradient_hessian(
1374
1375
  }
1375
1376
  let a1 = params.required_global("a1")?;
1376
1377
  let a2 = params.required_global("a2")?;
1377
- let reference_path = resolve_d3_reference_path(params, explicit_reference_path)?;
1378
- let reference = load_d3_reference(&reference_path)?;
1378
+ let reference = resolve_and_load_d3_reference(explicit_reference_path)?;
1379
1379
  let nat = system.atoms.len();
1380
1380
  let ndof = 3 * nat;
1381
1381
  let coords = jet_coordinates(system, ndof);
@@ -1474,8 +1474,7 @@ fn dispersion_strain_energy_jet(
1474
1474
  let atm_active = s9.abs() > 1.0e-15;
1475
1475
  let a1 = params.required_global("a1")?;
1476
1476
  let a2 = params.required_global("a2")?;
1477
- let reference_path = resolve_d3_reference_path(params, explicit_reference_path)?;
1478
- let reference = load_d3_reference(&reference_path)?;
1477
+ let reference = resolve_and_load_d3_reference(explicit_reference_path)?;
1479
1478
  let cn = d3_strain_coordination_jets(system, ndof)?;
1480
1479
  let weights = reference_weight_jets(system, &reference, &cn, ndof)?;
1481
1480
  let c6 = atomic_c6_jets(system, &reference, &weights, ndof)?;
@@ -1642,65 +1641,37 @@ fn d3_atm_triple_energy_strain_jet(
1642
1641
  pref.mul(&angular).mul(&fdmp)
1643
1642
  }
1644
1643
 
1645
- fn resolve_d3_reference_path(
1646
- params: &Gfn1Parameters,
1644
+ /// Resolve and load the D3 C6 reference data. An explicit path (the
1645
+ /// `--d3-reference` API argument) or the `GFN1_D3_REFERENCE` environment variable
1646
+ /// overrides the default; when neither is set, the reference bundled with the
1647
+ /// library and embedded at build time is used.
1648
+ fn resolve_and_load_d3_reference(
1647
1649
  explicit_reference_path: Option<&str>,
1648
- ) -> Result<PathBuf> {
1650
+ ) -> Result<Arc<D3Reference>> {
1649
1651
  if let Some(path) = explicit_reference_path {
1650
1652
  let trimmed = path.trim();
1651
1653
  if !trimmed.is_empty() {
1652
- return Ok(PathBuf::from(trimmed));
1654
+ return load_d3_reference(Path::new(trimmed));
1653
1655
  }
1654
1656
  }
1655
1657
  if let Ok(path) = std::env::var(GFN1_D3_REFERENCE_ENV) {
1656
1658
  let trimmed = path.trim();
1657
1659
  if !trimmed.is_empty() {
1658
- return Ok(PathBuf::from(trimmed));
1659
- }
1660
- }
1661
-
1662
- if let Some(param_path) = &params.source_path {
1663
- let param = Path::new(param_path);
1664
- if let Some(param_dir) = param.parent() {
1665
- if let Some(root) = param_dir.parent() {
1666
- let candidate = root
1667
- .join("tblite")
1668
- .join("subprojects")
1669
- .join("s-dftd3")
1670
- .join("src")
1671
- .join("dftd3")
1672
- .join("reference.f90");
1673
- if candidate.exists() {
1674
- return Ok(candidate);
1675
- }
1676
- }
1677
- let candidate = param_dir
1678
- .join("..")
1679
- .join("tblite")
1680
- .join("subprojects")
1681
- .join("s-dftd3")
1682
- .join("src")
1683
- .join("dftd3")
1684
- .join("reference.f90");
1685
- if candidate.exists() {
1686
- return Ok(candidate);
1687
- }
1660
+ return load_d3_reference(Path::new(trimmed));
1688
1661
  }
1689
1662
  }
1663
+ bundled_d3_reference()
1664
+ }
1690
1665
 
1691
- let bundled = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
1692
- .join("third_party")
1693
- .join("simple-dftd3")
1694
- .join("src")
1695
- .join("dftd3")
1696
- .join("reference.f90");
1697
- if bundled.exists() {
1698
- return Ok(bundled);
1666
+ /// Parse and cache the D3 reference embedded in the library.
1667
+ fn bundled_d3_reference() -> Result<Arc<D3Reference>> {
1668
+ static CACHE: OnceLock<Arc<D3Reference>> = OnceLock::new();
1669
+ if let Some(found) = CACHE.get() {
1670
+ return Ok(found.clone());
1699
1671
  }
1700
-
1701
- Err(Gfn1Error::InvalidInput(format!(
1702
- "missing D3 reference data; pass --d3-reference FILE or set {GFN1_D3_REFERENCE_ENV}"
1703
- )))
1672
+ let loaded = Arc::new(D3Reference::bundled()?);
1673
+ let _ = CACHE.set(loaded.clone());
1674
+ Ok(loaded)
1704
1675
  }
1705
1676
 
1706
1677
  #[derive(Clone, Debug)]
@@ -2777,8 +2748,7 @@ pub fn dispersion_third_derivative(
2777
2748
  }
2778
2749
  let a1 = params.required_global("a1")?;
2779
2750
  let a2 = params.required_global("a2")?;
2780
- let reference_path = resolve_d3_reference_path(params, explicit_reference_path)?;
2781
- let reference = load_d3_reference(&reference_path)?;
2751
+ let reference = resolve_and_load_d3_reference(explicit_reference_path)?;
2782
2752
  let nat = system.atoms.len();
2783
2753
  let ndof = 3 * nat;
2784
2754
  let coords = jet3_coordinates(system, ndof);
@@ -2827,10 +2797,18 @@ struct D3Reference {
2827
2797
  r4r2: Vec<f64>,
2828
2798
  }
2829
2799
 
2800
+ /// D3 C6 reference table and `r4/r2` data, bundled under `third_party/simple-dftd3`
2801
+ /// and embedded at build time so the default dispersion path needs no external
2802
+ /// file. Overridable through the `--d3-reference` API argument or the
2803
+ /// `GFN1_D3_REFERENCE` environment variable.
2804
+ const BUNDLED_D3_REFERENCE_F90: &str =
2805
+ include_str!("../third_party/simple-dftd3/src/dftd3/reference.f90");
2806
+ const BUNDLED_D3_R4R2_F90: &str =
2807
+ include_str!("../third_party/simple-dftd3/src/dftd3/data/r4r2.f90");
2808
+
2830
2809
  impl D3Reference {
2831
- fn from_reference_file(path: &Path) -> Result<Self> {
2832
- let text = fs::read_to_string(path)?;
2833
- let clean = strip_fortran_comments(&text);
2810
+ fn from_reference_text(reference_text: &str, r4r2_text: &str) -> Result<Self> {
2811
+ let clean = strip_fortran_comments(reference_text);
2834
2812
  let max_elem = parse_parameter_usize(&clean, "max_elem")?;
2835
2813
  let max_ref = parse_parameter_usize(&clean, "max_ref")?;
2836
2814
  let number_of_references = parse_int_array_after(&clean, "number_of_references")?
@@ -2859,14 +2837,7 @@ impl D3Reference {
2859
2837
  c6.len()
2860
2838
  )));
2861
2839
  }
2862
- let r4r2_path = path
2863
- .parent()
2864
- .ok_or_else(|| {
2865
- Gfn1Error::InvalidInput("D3 reference path has no parent directory".to_string())
2866
- })?
2867
- .join("data")
2868
- .join("r4r2.f90");
2869
- let r4r2 = parse_r4r2_file(&r4r2_path)?;
2840
+ let r4r2 = parse_r4r2_text(r4r2_text)?;
2870
2841
  Ok(Self {
2871
2842
  max_elem,
2872
2843
  max_ref,
@@ -2877,6 +2848,27 @@ impl D3Reference {
2877
2848
  })
2878
2849
  }
2879
2850
 
2851
+ /// Parse the D3 reference from a `reference.f90` file, reading the companion
2852
+ /// `data/r4r2.f90` next to it. Used only when an explicit path or the
2853
+ /// `GFN1_D3_REFERENCE` environment variable overrides the bundled default.
2854
+ fn from_reference_file(path: &Path) -> Result<Self> {
2855
+ let reference_text = fs::read_to_string(path)?;
2856
+ let r4r2_path = path
2857
+ .parent()
2858
+ .ok_or_else(|| {
2859
+ Gfn1Error::InvalidInput("D3 reference path has no parent directory".to_string())
2860
+ })?
2861
+ .join("data")
2862
+ .join("r4r2.f90");
2863
+ let r4r2_text = fs::read_to_string(&r4r2_path)?;
2864
+ Self::from_reference_text(&reference_text, &r4r2_text)
2865
+ }
2866
+
2867
+ /// The D3 reference bundled with the library and embedded at build time.
2868
+ fn bundled() -> Result<Self> {
2869
+ Self::from_reference_text(BUNDLED_D3_REFERENCE_F90, BUNDLED_D3_R4R2_F90)
2870
+ }
2871
+
2880
2872
  fn number_of_references(&self, z: u8) -> Result<usize> {
2881
2873
  self.number_of_references
2882
2874
  .get(z as usize - 1)
@@ -2944,9 +2936,8 @@ fn load_d3_reference(path: &Path) -> Result<Arc<D3Reference>> {
2944
2936
  Ok(loaded)
2945
2937
  }
2946
2938
 
2947
- fn parse_r4r2_file(path: &Path) -> Result<Vec<f64>> {
2948
- let text = fs::read_to_string(path)?;
2949
- let clean = strip_fortran_comments(&text);
2939
+ fn parse_r4r2_text(text: &str) -> Result<Vec<f64>> {
2940
+ let clean = strip_fortran_comments(text);
2950
2941
  let raw = parse_float_array_after(&clean, "r4_over_r2")?;
2951
2942
  Ok(raw
2952
2943
  .into_iter()
@@ -3082,8 +3073,8 @@ fn push_float_token(out: &mut Vec<f64>, token: &str) -> Result<()> {
3082
3073
  mod tests {
3083
3074
  use super::{
3084
3075
  dispersion_energy, dispersion_energy_gradient, dispersion_energy_gradient_hessian,
3085
- dispersion_third_derivative, load_d3_reference, reference_weight_cn_derivatives,
3086
- resolve_d3_reference_path,
3076
+ dispersion_third_derivative, reference_weight_cn_derivatives,
3077
+ resolve_and_load_d3_reference,
3087
3078
  };
3088
3079
  use crate::lattice::Lattice;
3089
3080
  use crate::math::{Mat3, Vec3};
@@ -3152,9 +3143,7 @@ mod tests {
3152
3143
 
3153
3144
  #[test]
3154
3145
  fn bundled_d3_reference_is_available() {
3155
- let params = Gfn1Parameters::default();
3156
- let path = resolve_d3_reference_path(&params, None).unwrap();
3157
- let reference = load_d3_reference(&path).unwrap();
3146
+ let reference = resolve_and_load_d3_reference(None).unwrap();
3158
3147
  assert!(reference.number_of_references(6).unwrap() > 0);
3159
3148
  assert!(reference.r4r2(8).unwrap() > 0.0);
3160
3149
  }
@@ -257,6 +257,10 @@ pub fn analytic_hessian_from_result(
257
257
  } else {
258
258
  None
259
259
  };
260
+ // The assembled Hessian is symmetric in exact arithmetic; average it with its
261
+ // transpose to remove the small asymmetry left by finite-precision accumulation
262
+ // across the per-term contributions.
263
+ symmetrize_in_place(&mut hessian);
260
264
  Ok(AnalyticHessianResult {
261
265
  hessian,
262
266
  repulsion,
@@ -501,6 +505,7 @@ pub fn fixed_density_pulay_hessian(
501
505
  /// geometric channels — `part_c_sab` = the `C·S_ab` (overlap-coeff × overlap-2nd-deriv) channel, and
502
506
  /// `part_h0` = the `2p·(h0_c·S_r + h0_r·S_c + S·h0_rc)` (h0-derivative) channel. Same loop/weights as
503
507
  /// [`fixed_density_pulay_hessian`]; used to FD-attribute the reconverged density-path residual `miss[c]`.
508
+ #[cfg(test)]
504
509
  pub(crate) fn fixed_density_pulay_hessian_parts(
505
510
  system: &PeriodicSystem,
506
511
  params: &Gfn1Parameters,
@@ -578,6 +583,7 @@ pub(crate) fn fixed_density_pulay_hessian_parts(
578
583
  /// Their sum is exactly `part_c_sab` from [`fixed_density_pulay_hessian_parts`]. Used to attribute the
579
584
  /// reconverged density-path residual `miss_csab` to the P·2h0 / −P·V / −2W sub-channels (the −2W block is
580
585
  /// the EWD/W channel; its analytic linearized density-path is `−2·W^(c)·d²S_ab`).
586
+ #[cfg(test)]
581
587
  pub(crate) fn fixed_density_pulay_hessian_csab_subparts(
582
588
  system: &PeriodicSystem,
583
589
  params: &Gfn1Parameters,
@@ -2308,6 +2314,20 @@ fn add_matrix(lhs: &mut Matrix, rhs: &Matrix) -> Result<()> {
2308
2314
  Ok(())
2309
2315
  }
2310
2316
 
2317
+ /// Replace a square matrix with `½(H + Hᵀ)`, enforcing exact symmetry on the
2318
+ /// final Hessian.
2319
+ fn symmetrize_in_place(hessian: &mut Matrix) {
2320
+ let n = hessian.rows();
2321
+ debug_assert_eq!(n, hessian.cols(), "Hessian must be square to symmetrize");
2322
+ for i in 0..n {
2323
+ for j in (i + 1)..n {
2324
+ let avg = 0.5 * (hessian[(i, j)] + hessian[(j, i)]);
2325
+ hessian[(i, j)] = avg;
2326
+ hessian[(j, i)] = avg;
2327
+ }
2328
+ }
2329
+ }
2330
+
2311
2331
  type Ten3 = [[[f64; 3]; 3]; 3];
2312
2332
 
2313
2333
  /// `H0` (or bare `poly`) value plus its bra/ket Cartesian derivative tensors up to **third**
@@ -2677,6 +2697,7 @@ pub fn fixed_density_pulay_third_derivative(
2677
2697
  ///
2678
2698
  /// `response_density`=P^(c), `response_ew_density`=W^(c), `response_scalar_potential`=V^(c). Returned
2679
2699
  /// ordered slabs `tensor[c][(a,b)]`. `only_channel`: Some(0)=C:S_ab only, Some(1)=h0 only, None=both.
2700
+ #[cfg(test)]
2680
2701
  pub(crate) fn pulay_density_path_geom_cross_ordered(
2681
2702
  system: &PeriodicSystem,
2682
2703
  params: &Gfn1Parameters,
@@ -2816,120 +2837,6 @@ pub(crate) fn pulay_density_path_geom_cross_ordered(
2816
2837
  Ok(tensor)
2817
2838
  }
2818
2839
 
2819
- /// PULAY density-path GEOMETRY×RESPONSE cross term (the missing 3rd-derivative piece). The linearized
2820
- /// density-path `pulay(P^(c),W^(c),V) + [pulay(V+V^(c))−pulay(V)]` reproduces the pulay Hessian with the
2821
- /// RESPONSE coefficient `C^(c) = P^(c)(2h0−V) + P(−V^(c)) − 2W^(c)` and `two_p^(c) = 2P^(c)`, but at FIXED
2822
- /// geometry — it omits the ONE geometry derivative (slab `c`) acting on that already-response coefficient's
2823
- /// overlap / h0 factors. This function supplies exactly that: the geometric slab-`c` derivative of the
2824
- /// pulay Hessian body with the coefficient held at the linear response `(C^(c), two_p^(c))`. Same overlap /
2825
- /// h0 third-derivative machinery and weights as [`fixed_density_pulay_third_derivative`]; the coefficient
2826
- /// is the response, the geometry derivatives are the base geometry. First-order-response only.
2827
- ///
2828
- /// `response_density` = P^(c), `response_ew_density` = W^(c), `response_scalar_potential` = V^(c) (the shell
2829
- /// SCC-potential response). Returned ordered slabs `tensor[c][(a,b)]`.
2830
- pub(crate) fn pulay_density_path_geom_cross(
2831
- system: &PeriodicSystem,
2832
- params: &Gfn1Parameters,
2833
- electronic: &ElectronicResult,
2834
- response_density: &Matrix,
2835
- response_ew_density: &Matrix,
2836
- response_scalar_potential: &[f64],
2837
- ) -> Result<Vec<Matrix>> {
2838
- ensure_non_pbc(system)?;
2839
- let nat = system.atoms.len();
2840
- let ndof = 3 * nat;
2841
- let basis = &electronic.basis;
2842
- let mut tensor = vec![Matrix::zeros(ndof, ndof); ndof];
2843
- // Base and response AO scalar potentials.
2844
- let ao_v = ao_scalar_potentials(basis, &electronic.shell_scc_potential);
2845
- let ao_vc = ao_scalar_potentials(basis, response_scalar_potential);
2846
-
2847
- for mu in 0..basis.len() {
2848
- let atom_mu = basis.aos[mu].atom_index;
2849
- let shell_mu_index = basis.aos[mu].shell_index;
2850
- let rmu = system.atoms[atom_mu].position;
2851
- for nu in 0..mu {
2852
- let atom_nu = basis.aos[nu].atom_index;
2853
- if atom_mu == atom_nu {
2854
- continue;
2855
- }
2856
- let shell_nu_index = basis.aos[nu].shell_index;
2857
- let rnu = system.atoms[atom_nu].position;
2858
- let p = electronic.density[(mu, nu)];
2859
- let pc = response_density[(mu, nu)];
2860
- let wc = response_ew_density[(mu, nu)];
2861
- if p.abs().max(pc.abs()).max(wc.abs()) <= 1.0e-18 {
2862
- continue;
2863
- }
2864
- let pair =
2865
- contracted_pair_with_third_derivatives(&basis.aos[mu], &basis.aos[nu], rmu, rnu);
2866
- let overlap = pair.moments[0];
2867
- let h0 =
2868
- h0_prefactor_third(system, params, electronic, shell_mu_index, shell_nu_index)?;
2869
- let scalar_shift = ao_v[mu] + ao_v[nu];
2870
- let scalar_shift_c = ao_vc[mu] + ao_vc[nu];
2871
- // Response coefficient: C^(c) = P^(c)(2h0−V) + P(−V^(c)) − 2W^(c); two_p^(c) = 2 P^(c).
2872
- let overlap_coeff = pc * (2.0 * h0.value - scalar_shift) - p * scalar_shift_c - 2.0 * wc;
2873
- let two_p = 2.0 * pc;
2874
-
2875
- let atom_of = |c: Center| match c {
2876
- Center::Bra => atom_mu,
2877
- Center::Ket => atom_nu,
2878
- };
2879
- let s1 = |c: Center, ax: usize| first(&pair.d_bra[0], &pair.d_ket[0], c, ax);
2880
- let s2 = |ca: Center, cb: Center, axa: usize, axb: usize| {
2881
- second(&pair.h_bra_bra[0], &pair.h_bra_ket[0], &pair.h_ket_ket[0], ca, cb, axa, axb)
2882
- };
2883
- let h1 = |c: Center, ax: usize| first_vec(h0.d_bra, h0.d_ket, c, ax);
2884
- let h2 = |ca: Center, cb: Center, axa: usize, axb: usize| {
2885
- second(&h0.h_bra_bra, &h0.h_bra_ket, &h0.h_ket_ket, ca, cb, axa, axb)
2886
- };
2887
- let slot_center = |s: usize| if s < 3 { Center::Bra } else { Center::Ket };
2888
- let value_of = |i: usize, j: usize, k: usize| -> f64 {
2889
- let (ca, axa) = (slot_center(i), i % 3);
2890
- let (cb, axb) = (slot_center(j), j % 3);
2891
- let (cc, axc) = (slot_center(k), k % 3);
2892
- let s_abc = third_select(
2893
- &pair.t_bra_bra_bra[0], &pair.t_bra_bra_ket[0], &pair.t_bra_ket_ket[0],
2894
- &pair.t_ket_ket_ket[0], [ca, cb, cc], [axa, axb, axc],
2895
- );
2896
- let h_abc = third_select(
2897
- &h0.t_bra_bra_bra, &h0.t_bra_bra_ket, &h0.t_bra_ket_ket,
2898
- &h0.t_ket_ket_ket, [ca, cb, cc], [axa, axb, axc],
2899
- );
2900
- let (s_a, s_b, s_c) = (s1(ca, axa), s1(cb, axb), s1(cc, axc));
2901
- let s_ab = s2(ca, cb, axa, axb);
2902
- let s_ac = s2(ca, cc, axa, axc);
2903
- let s_bc = s2(cb, cc, axb, axc);
2904
- let (h_a, h_b, h_c) = (h1(ca, axa), h1(cb, axb), h1(cc, axc));
2905
- let h_ab = h2(ca, cb, axa, axb);
2906
- let h_ac = h2(ca, cc, axa, axc);
2907
- let h_bc = h2(cb, cc, axb, axc);
2908
- overlap_coeff * s_abc
2909
- + two_p
2910
- * (overlap * h_abc
2911
- + (h_ab * s_c + h_ac * s_b + h_bc * s_a)
2912
- + (h_a * s_bc + h_b * s_ac + h_c * s_ab))
2913
- };
2914
- let dof = |s: usize| 3 * atom_of(slot_center(s)) + s % 3;
2915
- for i in 0..6 {
2916
- for j in i..6 {
2917
- for k in j..6 {
2918
- let v = value_of(i, j, k);
2919
- if v == 0.0 {
2920
- continue;
2921
- }
2922
- for &(pp, qq, rr) in &distinct_perms(i, j, k) {
2923
- tensor[dof(rr)][(dof(pp), dof(qq))] += v;
2924
- }
2925
- }
2926
- }
2927
- }
2928
- }
2929
- }
2930
- Ok(tensor)
2931
- }
2932
-
2933
2840
  #[cfg(test)]
2934
2841
  mod third_derivative_tests {
2935
2842
  use super::*;
File without changes
File without changes
File without changes