MultiOptPy 1.20.2__py3-none-any.whl

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 (246) hide show
  1. multioptpy/Calculator/__init__.py +0 -0
  2. multioptpy/Calculator/ase_calculation_tools.py +424 -0
  3. multioptpy/Calculator/ase_tools/__init__.py +0 -0
  4. multioptpy/Calculator/ase_tools/fairchem.py +28 -0
  5. multioptpy/Calculator/ase_tools/gamess.py +19 -0
  6. multioptpy/Calculator/ase_tools/gaussian.py +165 -0
  7. multioptpy/Calculator/ase_tools/mace.py +28 -0
  8. multioptpy/Calculator/ase_tools/mopac.py +19 -0
  9. multioptpy/Calculator/ase_tools/nwchem.py +31 -0
  10. multioptpy/Calculator/ase_tools/orca.py +22 -0
  11. multioptpy/Calculator/ase_tools/pygfn0.py +37 -0
  12. multioptpy/Calculator/dxtb_calculation_tools.py +344 -0
  13. multioptpy/Calculator/emt_calculation_tools.py +458 -0
  14. multioptpy/Calculator/gpaw_calculation_tools.py +183 -0
  15. multioptpy/Calculator/lj_calculation_tools.py +314 -0
  16. multioptpy/Calculator/psi4_calculation_tools.py +334 -0
  17. multioptpy/Calculator/pwscf_calculation_tools.py +189 -0
  18. multioptpy/Calculator/pyscf_calculation_tools.py +327 -0
  19. multioptpy/Calculator/sqm1_calculation_tools.py +611 -0
  20. multioptpy/Calculator/sqm2_calculation_tools.py +376 -0
  21. multioptpy/Calculator/tblite_calculation_tools.py +352 -0
  22. multioptpy/Calculator/tersoff_calculation_tools.py +818 -0
  23. multioptpy/Constraint/__init__.py +0 -0
  24. multioptpy/Constraint/constraint_condition.py +834 -0
  25. multioptpy/Coordinate/__init__.py +0 -0
  26. multioptpy/Coordinate/polar_coordinate.py +199 -0
  27. multioptpy/Coordinate/redundant_coordinate.py +638 -0
  28. multioptpy/IRC/__init__.py +0 -0
  29. multioptpy/IRC/converge_criteria.py +28 -0
  30. multioptpy/IRC/dvv.py +544 -0
  31. multioptpy/IRC/euler.py +439 -0
  32. multioptpy/IRC/hpc.py +564 -0
  33. multioptpy/IRC/lqa.py +540 -0
  34. multioptpy/IRC/modekill.py +662 -0
  35. multioptpy/IRC/rk4.py +579 -0
  36. multioptpy/Interpolation/__init__.py +0 -0
  37. multioptpy/Interpolation/adaptive_interpolation.py +283 -0
  38. multioptpy/Interpolation/binomial_interpolation.py +179 -0
  39. multioptpy/Interpolation/geodesic_interpolation.py +785 -0
  40. multioptpy/Interpolation/interpolation.py +156 -0
  41. multioptpy/Interpolation/linear_interpolation.py +473 -0
  42. multioptpy/Interpolation/savitzky_golay_interpolation.py +252 -0
  43. multioptpy/Interpolation/spline_interpolation.py +353 -0
  44. multioptpy/MD/__init__.py +0 -0
  45. multioptpy/MD/thermostat.py +185 -0
  46. multioptpy/MEP/__init__.py +0 -0
  47. multioptpy/MEP/pathopt_bneb_force.py +443 -0
  48. multioptpy/MEP/pathopt_dmf_force.py +448 -0
  49. multioptpy/MEP/pathopt_dneb_force.py +130 -0
  50. multioptpy/MEP/pathopt_ewbneb_force.py +207 -0
  51. multioptpy/MEP/pathopt_gpneb_force.py +512 -0
  52. multioptpy/MEP/pathopt_lup_force.py +113 -0
  53. multioptpy/MEP/pathopt_neb_force.py +225 -0
  54. multioptpy/MEP/pathopt_nesb_force.py +205 -0
  55. multioptpy/MEP/pathopt_om_force.py +153 -0
  56. multioptpy/MEP/pathopt_qsm_force.py +174 -0
  57. multioptpy/MEP/pathopt_qsmv2_force.py +304 -0
  58. multioptpy/ModelFunction/__init__.py +7 -0
  59. multioptpy/ModelFunction/avoiding_model_function.py +29 -0
  60. multioptpy/ModelFunction/binary_image_ts_search_model_function.py +47 -0
  61. multioptpy/ModelFunction/conical_model_function.py +26 -0
  62. multioptpy/ModelFunction/opt_meci.py +50 -0
  63. multioptpy/ModelFunction/opt_mesx.py +47 -0
  64. multioptpy/ModelFunction/opt_mesx_2.py +49 -0
  65. multioptpy/ModelFunction/seam_model_function.py +27 -0
  66. multioptpy/ModelHessian/__init__.py +0 -0
  67. multioptpy/ModelHessian/approx_hessian.py +147 -0
  68. multioptpy/ModelHessian/calc_params.py +227 -0
  69. multioptpy/ModelHessian/fischer.py +236 -0
  70. multioptpy/ModelHessian/fischerd3.py +360 -0
  71. multioptpy/ModelHessian/fischerd4.py +398 -0
  72. multioptpy/ModelHessian/gfn0xtb.py +633 -0
  73. multioptpy/ModelHessian/gfnff.py +709 -0
  74. multioptpy/ModelHessian/lindh.py +165 -0
  75. multioptpy/ModelHessian/lindh2007d2.py +707 -0
  76. multioptpy/ModelHessian/lindh2007d3.py +822 -0
  77. multioptpy/ModelHessian/lindh2007d4.py +1030 -0
  78. multioptpy/ModelHessian/morse.py +106 -0
  79. multioptpy/ModelHessian/schlegel.py +144 -0
  80. multioptpy/ModelHessian/schlegeld3.py +322 -0
  81. multioptpy/ModelHessian/schlegeld4.py +559 -0
  82. multioptpy/ModelHessian/shortrange.py +346 -0
  83. multioptpy/ModelHessian/swartd2.py +496 -0
  84. multioptpy/ModelHessian/swartd3.py +706 -0
  85. multioptpy/ModelHessian/swartd4.py +918 -0
  86. multioptpy/ModelHessian/tshess.py +40 -0
  87. multioptpy/Optimizer/QHAdam.py +61 -0
  88. multioptpy/Optimizer/__init__.py +0 -0
  89. multioptpy/Optimizer/abc_fire.py +83 -0
  90. multioptpy/Optimizer/adabelief.py +58 -0
  91. multioptpy/Optimizer/adabound.py +68 -0
  92. multioptpy/Optimizer/adadelta.py +65 -0
  93. multioptpy/Optimizer/adaderivative.py +56 -0
  94. multioptpy/Optimizer/adadiff.py +68 -0
  95. multioptpy/Optimizer/adafactor.py +70 -0
  96. multioptpy/Optimizer/adam.py +65 -0
  97. multioptpy/Optimizer/adamax.py +62 -0
  98. multioptpy/Optimizer/adamod.py +83 -0
  99. multioptpy/Optimizer/adamw.py +65 -0
  100. multioptpy/Optimizer/adiis.py +523 -0
  101. multioptpy/Optimizer/afire_neb.py +282 -0
  102. multioptpy/Optimizer/block_hessian_update.py +709 -0
  103. multioptpy/Optimizer/c2diis.py +491 -0
  104. multioptpy/Optimizer/component_wise_scaling.py +405 -0
  105. multioptpy/Optimizer/conjugate_gradient.py +82 -0
  106. multioptpy/Optimizer/conjugate_gradient_neb.py +345 -0
  107. multioptpy/Optimizer/coordinate_locking.py +405 -0
  108. multioptpy/Optimizer/dic_rsirfo.py +1015 -0
  109. multioptpy/Optimizer/ediis.py +417 -0
  110. multioptpy/Optimizer/eve.py +76 -0
  111. multioptpy/Optimizer/fastadabelief.py +61 -0
  112. multioptpy/Optimizer/fire.py +77 -0
  113. multioptpy/Optimizer/fire2.py +249 -0
  114. multioptpy/Optimizer/fire_neb.py +92 -0
  115. multioptpy/Optimizer/gan_step.py +486 -0
  116. multioptpy/Optimizer/gdiis.py +609 -0
  117. multioptpy/Optimizer/gediis.py +203 -0
  118. multioptpy/Optimizer/geodesic_step.py +433 -0
  119. multioptpy/Optimizer/gpmin.py +633 -0
  120. multioptpy/Optimizer/gpr_step.py +364 -0
  121. multioptpy/Optimizer/gradientdescent.py +78 -0
  122. multioptpy/Optimizer/gradientdescent_neb.py +52 -0
  123. multioptpy/Optimizer/hessian_update.py +433 -0
  124. multioptpy/Optimizer/hybrid_rfo.py +998 -0
  125. multioptpy/Optimizer/kdiis.py +625 -0
  126. multioptpy/Optimizer/lars.py +21 -0
  127. multioptpy/Optimizer/lbfgs.py +253 -0
  128. multioptpy/Optimizer/lbfgs_neb.py +355 -0
  129. multioptpy/Optimizer/linesearch.py +236 -0
  130. multioptpy/Optimizer/lookahead.py +40 -0
  131. multioptpy/Optimizer/nadam.py +64 -0
  132. multioptpy/Optimizer/newton.py +200 -0
  133. multioptpy/Optimizer/prodigy.py +70 -0
  134. multioptpy/Optimizer/purtubation.py +16 -0
  135. multioptpy/Optimizer/quickmin_neb.py +245 -0
  136. multioptpy/Optimizer/radam.py +75 -0
  137. multioptpy/Optimizer/rfo_neb.py +302 -0
  138. multioptpy/Optimizer/ric_rfo.py +842 -0
  139. multioptpy/Optimizer/rl_step.py +627 -0
  140. multioptpy/Optimizer/rmspropgrave.py +65 -0
  141. multioptpy/Optimizer/rsirfo.py +1647 -0
  142. multioptpy/Optimizer/rsprfo.py +1056 -0
  143. multioptpy/Optimizer/sadam.py +60 -0
  144. multioptpy/Optimizer/samsgrad.py +63 -0
  145. multioptpy/Optimizer/tr_lbfgs.py +678 -0
  146. multioptpy/Optimizer/trim.py +273 -0
  147. multioptpy/Optimizer/trust_radius.py +207 -0
  148. multioptpy/Optimizer/trust_radius_neb.py +121 -0
  149. multioptpy/Optimizer/yogi.py +60 -0
  150. multioptpy/OtherMethod/__init__.py +0 -0
  151. multioptpy/OtherMethod/addf.py +1150 -0
  152. multioptpy/OtherMethod/dimer.py +895 -0
  153. multioptpy/OtherMethod/elastic_image_pair.py +629 -0
  154. multioptpy/OtherMethod/modelfunction.py +456 -0
  155. multioptpy/OtherMethod/newton_traj.py +454 -0
  156. multioptpy/OtherMethod/twopshs.py +1095 -0
  157. multioptpy/PESAnalyzer/__init__.py +0 -0
  158. multioptpy/PESAnalyzer/calc_irc_curvature.py +125 -0
  159. multioptpy/PESAnalyzer/cmds_analysis.py +152 -0
  160. multioptpy/PESAnalyzer/koopman_analysis.py +268 -0
  161. multioptpy/PESAnalyzer/pca_analysis.py +314 -0
  162. multioptpy/Parameters/__init__.py +0 -0
  163. multioptpy/Parameters/atomic_mass.py +20 -0
  164. multioptpy/Parameters/atomic_number.py +22 -0
  165. multioptpy/Parameters/covalent_radii.py +44 -0
  166. multioptpy/Parameters/d2.py +61 -0
  167. multioptpy/Parameters/d3.py +63 -0
  168. multioptpy/Parameters/d4.py +103 -0
  169. multioptpy/Parameters/dreiding.py +34 -0
  170. multioptpy/Parameters/gfn0xtb_param.py +137 -0
  171. multioptpy/Parameters/gfnff_param.py +315 -0
  172. multioptpy/Parameters/gnb.py +104 -0
  173. multioptpy/Parameters/parameter.py +22 -0
  174. multioptpy/Parameters/uff.py +72 -0
  175. multioptpy/Parameters/unit_values.py +20 -0
  176. multioptpy/Potential/AFIR_potential.py +55 -0
  177. multioptpy/Potential/LJ_repulsive_potential.py +345 -0
  178. multioptpy/Potential/__init__.py +0 -0
  179. multioptpy/Potential/anharmonic_keep_potential.py +28 -0
  180. multioptpy/Potential/asym_elllipsoidal_potential.py +718 -0
  181. multioptpy/Potential/electrostatic_potential.py +69 -0
  182. multioptpy/Potential/flux_potential.py +30 -0
  183. multioptpy/Potential/gaussian_potential.py +101 -0
  184. multioptpy/Potential/idpp.py +516 -0
  185. multioptpy/Potential/keep_angle_potential.py +146 -0
  186. multioptpy/Potential/keep_dihedral_angle_potential.py +105 -0
  187. multioptpy/Potential/keep_outofplain_angle_potential.py +70 -0
  188. multioptpy/Potential/keep_potential.py +99 -0
  189. multioptpy/Potential/mechano_force_potential.py +74 -0
  190. multioptpy/Potential/nanoreactor_potential.py +52 -0
  191. multioptpy/Potential/potential.py +896 -0
  192. multioptpy/Potential/spacer_model_potential.py +221 -0
  193. multioptpy/Potential/switching_potential.py +258 -0
  194. multioptpy/Potential/universal_potential.py +34 -0
  195. multioptpy/Potential/value_range_potential.py +36 -0
  196. multioptpy/Potential/void_point_potential.py +25 -0
  197. multioptpy/SQM/__init__.py +0 -0
  198. multioptpy/SQM/sqm1/__init__.py +0 -0
  199. multioptpy/SQM/sqm1/sqm1_core.py +1792 -0
  200. multioptpy/SQM/sqm2/__init__.py +0 -0
  201. multioptpy/SQM/sqm2/calc_tools.py +95 -0
  202. multioptpy/SQM/sqm2/sqm2_basis.py +850 -0
  203. multioptpy/SQM/sqm2/sqm2_bond.py +119 -0
  204. multioptpy/SQM/sqm2/sqm2_core.py +303 -0
  205. multioptpy/SQM/sqm2/sqm2_data.py +1229 -0
  206. multioptpy/SQM/sqm2/sqm2_disp.py +65 -0
  207. multioptpy/SQM/sqm2/sqm2_eeq.py +243 -0
  208. multioptpy/SQM/sqm2/sqm2_overlapint.py +704 -0
  209. multioptpy/SQM/sqm2/sqm2_qm.py +578 -0
  210. multioptpy/SQM/sqm2/sqm2_rep.py +66 -0
  211. multioptpy/SQM/sqm2/sqm2_srb.py +70 -0
  212. multioptpy/Thermo/__init__.py +0 -0
  213. multioptpy/Thermo/normal_mode_analyzer.py +865 -0
  214. multioptpy/Utils/__init__.py +0 -0
  215. multioptpy/Utils/bond_connectivity.py +264 -0
  216. multioptpy/Utils/calc_tools.py +884 -0
  217. multioptpy/Utils/oniom.py +96 -0
  218. multioptpy/Utils/pbc.py +48 -0
  219. multioptpy/Utils/riemann_curvature.py +208 -0
  220. multioptpy/Utils/symmetry_analyzer.py +482 -0
  221. multioptpy/Visualization/__init__.py +0 -0
  222. multioptpy/Visualization/visualization.py +156 -0
  223. multioptpy/WFAnalyzer/MO_analysis.py +104 -0
  224. multioptpy/WFAnalyzer/__init__.py +0 -0
  225. multioptpy/Wrapper/__init__.py +0 -0
  226. multioptpy/Wrapper/autots.py +1239 -0
  227. multioptpy/Wrapper/ieip_wrapper.py +93 -0
  228. multioptpy/Wrapper/md_wrapper.py +92 -0
  229. multioptpy/Wrapper/neb_wrapper.py +94 -0
  230. multioptpy/Wrapper/optimize_wrapper.py +76 -0
  231. multioptpy/__init__.py +5 -0
  232. multioptpy/entrypoints.py +916 -0
  233. multioptpy/fileio.py +660 -0
  234. multioptpy/ieip.py +340 -0
  235. multioptpy/interface.py +1086 -0
  236. multioptpy/irc.py +529 -0
  237. multioptpy/moleculardynamics.py +432 -0
  238. multioptpy/neb.py +1267 -0
  239. multioptpy/optimization.py +1553 -0
  240. multioptpy/optimizer.py +709 -0
  241. multioptpy-1.20.2.dist-info/METADATA +438 -0
  242. multioptpy-1.20.2.dist-info/RECORD +246 -0
  243. multioptpy-1.20.2.dist-info/WHEEL +5 -0
  244. multioptpy-1.20.2.dist-info/entry_points.txt +9 -0
  245. multioptpy-1.20.2.dist-info/licenses/LICENSE +674 -0
  246. multioptpy-1.20.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1030 @@
1
+ import numpy as np
2
+
3
+ from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib, element_number
4
+ from multioptpy.Utils.calc_tools import Calculationtools
5
+ from multioptpy.ModelHessian.calc_params import torsion2, outofplane2
6
+ from multioptpy.Parameters.parameter import D4Parameters, D2_C6_coeff_lib, UFF_VDW_distance_lib
7
+
8
+
9
+ class Lindh2007D4ApproxHessian:
10
+ """
11
+ Lindh's Model Hessian (2007) augmented with D4 dispersion correction.
12
+
13
+ This class implements Lindh's 2007 approximate Hessian model with D4 dispersion
14
+ corrections for improved accuracy in describing non-covalent interactions.
15
+
16
+ References:
17
+ - Lindh et al., Chem. Phys. Lett. 2007, 241, 423.
18
+ - Caldeweyher et al., J. Chem. Phys. 2019, 150, 154122 (DFT-D4).
19
+ - https://github.com/grimme-lab/xtb/blob/main/src/model_hessian.f90
20
+ """
21
+
22
+ def __init__(self):
23
+ # Unit conversion constants
24
+ self.bohr2angstroms = UnitValueLib().bohr2angstroms
25
+ self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
26
+
27
+ # Force constant parameters
28
+ self.bond_threshold_scale = 1.0
29
+ self.kr = 0.45 # Bond stretching force constant
30
+ self.kf = 0.10 # Angle bend force constant
31
+ self.kt = 0.0025 # Torsion force constant
32
+ self.ko = 0.16 # Out-of-plane force constant
33
+ self.kd = 0.05 # Dispersion force constant
34
+
35
+ # Numerical parameters
36
+ self.cutoff = 50.0 # Cutoff for long-range interactions (Bohr)
37
+ self.eps = 1.0e-12 # Numerical threshold for avoiding division by zero
38
+
39
+ # Reference parameters (element type matrices)
40
+ self.rAv = np.array([
41
+ [1.3500, 2.1000, 2.5300],
42
+ [2.1000, 2.8700, 3.8000],
43
+ [2.5300, 3.8000, 4.5000]
44
+ ])
45
+
46
+ self.aAv = np.array([
47
+ [1.0000, 0.3949, 0.3949],
48
+ [0.3949, 0.2800, 0.1200],
49
+ [0.3949, 0.1200, 0.0600]
50
+ ])
51
+
52
+ self.dAv = np.array([
53
+ [0.0000, 3.6000, 3.6000],
54
+ [3.6000, 5.3000, 5.3000],
55
+ [3.6000, 5.3000, 5.3000]
56
+ ])
57
+
58
+ # D4 dispersion parameters
59
+ self.d4params = D4Parameters()
60
+
61
+ def select_idx(self, elem_num):
62
+ """
63
+ Determine element group index for parameter selection.
64
+
65
+ Args:
66
+ elem_num (str or int): Element symbol or atomic number
67
+
68
+ Returns:
69
+ int: Group index (0-2) for parameter lookup
70
+ """
71
+ if isinstance(elem_num, str):
72
+ elem_num = element_number(elem_num)
73
+
74
+ # Group 1: H
75
+ if elem_num > 0 and elem_num < 2:
76
+ return 0
77
+ # Group 2: First row elements (Li-Ne)
78
+ elif elem_num >= 2 and elem_num < 10:
79
+ return 1
80
+ # Group 3: All others
81
+ else:
82
+ return 2
83
+
84
+ def calc_force_const(self, alpha, r_0, distance_2):
85
+ """
86
+ Calculate bond stretching force constant based on Lindh's model.
87
+
88
+ Args:
89
+ alpha: Exponential parameter
90
+ r_0: Reference bond length
91
+ distance_2: Squared distance between atoms
92
+
93
+ Returns:
94
+ float: Force constant
95
+ """
96
+ return np.exp(alpha * (r_0**2 - distance_2))
97
+
98
+ def get_c6_coefficient(self, element):
99
+ """
100
+ Get C6 dispersion coefficient for an element.
101
+
102
+ Args:
103
+ element: Element symbol
104
+
105
+ Returns:
106
+ float: C6 coefficient in atomic units
107
+ """
108
+ return D2_C6_coeff_lib(element)
109
+
110
+ def estimate_atomic_charges(self, coord, element_list):
111
+ """
112
+ Estimate atomic partial charges using electronegativity equilibration.
113
+
114
+ Args:
115
+ coord: Atomic coordinates (Bohr)
116
+ element_list: List of element symbols
117
+
118
+ Returns:
119
+ charges: Array of partial charges
120
+ """
121
+ n_atoms = len(coord)
122
+ charges = np.zeros(n_atoms)
123
+
124
+ # Detect bonds based on distance
125
+ bonds = []
126
+ for i in range(n_atoms):
127
+ for j in range(i+1, n_atoms):
128
+ r_ij = np.linalg.norm(coord[i] - coord[j])
129
+ r_cov_i = covalent_radii_lib(element_list[i])
130
+ r_cov_j = covalent_radii_lib(element_list[j])
131
+ r_sum = (r_cov_i + r_cov_j) * self.bond_threshold_scale
132
+
133
+ if r_ij < r_sum * 1.3: # 1.3 is a common threshold for bond detection
134
+ bonds.append((i, j))
135
+
136
+ # Estimate charges based on electronegativity differences
137
+ for i, j in bonds:
138
+ en_i = self.d4params.get_electronegativity(element_list[i])
139
+ en_j = self.d4params.get_electronegativity(element_list[j])
140
+
141
+ # Simple electronegativity-based charge transfer
142
+ en_diff = en_j - en_i
143
+ charge_transfer = 0.1 * np.tanh(0.2 * en_diff) # Sigmoidal scaling for stability
144
+
145
+ charges[i] += charge_transfer
146
+ charges[j] -= charge_transfer
147
+
148
+ # Normalize to ensure total charge is zero
149
+ charges -= np.mean(charges)
150
+
151
+ return charges
152
+
153
+ def calculate_coordination_numbers(self, coord, element_list):
154
+ """
155
+ Calculate atomic coordination numbers for scaling dispersion.
156
+
157
+ Args:
158
+ coord: Atomic coordinates (Bohr)
159
+ element_list: List of element symbols
160
+
161
+ Returns:
162
+ cn: Array of coordination numbers
163
+ """
164
+ n_atoms = len(coord)
165
+ cn = np.zeros(n_atoms)
166
+
167
+ # D4 uses a counting function based on interatomic distances
168
+ for i in range(n_atoms):
169
+ r_cov_i = covalent_radii_lib(element_list[i])
170
+
171
+ for j in range(n_atoms):
172
+ if i == j:
173
+ continue
174
+
175
+ r_cov_j = covalent_radii_lib(element_list[j])
176
+ r_ij = np.linalg.norm(coord[i] - coord[j])
177
+
178
+ # Coordination number contribution with Gaussian-like counting function
179
+ r_cov = r_cov_i + r_cov_j
180
+ k1 = 16.0 # Steepness parameter
181
+ cn_contrib = 1.0 / (1.0 + np.exp(-k1 * (r_cov/r_ij - 1.0)))
182
+ cn[i] += cn_contrib
183
+
184
+ return cn
185
+
186
+ def calc_d4_force_const(self, r_ij, c6_param, c8_param, r0_param, q_scaling=1.0):
187
+ """
188
+ Calculate D4 dispersion force constant with Becke-Johnson damping and charge scaling.
189
+
190
+ Args:
191
+ r_ij: Distance between atoms
192
+ c6_param: C6 dispersion coefficient
193
+ c8_param: C8 dispersion coefficient
194
+ r0_param: van der Waals radius sum parameter
195
+ q_scaling: Charge-dependent scaling factor
196
+
197
+ Returns:
198
+ float: D4 dispersion force constant
199
+ """
200
+ # Becke-Johnson damping function for C6 term
201
+ r0_plus_a1 = r0_param + self.d4params.a1
202
+ f_damp_6 = r_ij**6 / (r_ij**6 + (r0_plus_a1 * self.d4params.a2)**6)
203
+
204
+ # Becke-Johnson damping function for C8 term
205
+ f_damp_8 = r_ij**8 / (r_ij**8 + (r0_plus_a1 * self.d4params.a2)**8)
206
+
207
+ # Apply charge scaling to C6 and C8 coefficients
208
+ c6_scaled = c6_param * q_scaling
209
+ c8_scaled = c8_param * q_scaling
210
+
211
+ # D4 dispersion energy contributions
212
+ e6 = -self.d4params.s6 * c6_scaled * f_damp_6 / r_ij**6
213
+ e8 = -self.d4params.s8 * c8_scaled * f_damp_8 / r_ij**8
214
+
215
+ # Combined force constant (negative of energy for attractive contribution)
216
+ return -(e6 + e8)
217
+
218
+ def get_d4_parameters(self, elem1, elem2, q1=0.0, q2=0.0):
219
+ """
220
+ Get D4 parameters for a pair of elements with charge scaling.
221
+
222
+ Args:
223
+ elem1: First element symbol
224
+ elem2: Second element symbol
225
+ q1: Partial charge on first atom
226
+ q2: Partial charge on second atom
227
+
228
+ Returns:
229
+ tuple: (c6_param, c8_param, r0_param, q_scaling) for the element pair
230
+ """
231
+ # Get reference polarizabilities
232
+ alpha_1 = self.d4params.get_polarizability(elem1)
233
+ alpha_2 = self.d4params.get_polarizability(elem2)
234
+
235
+ # Get base C6 coefficients
236
+ c6_1 = self.get_c6_coefficient(elem1)
237
+ c6_2 = self.get_c6_coefficient(elem2)
238
+
239
+ # Combine C6 coefficients with Casimir-Polder formula
240
+ c6_param = 2.0 * c6_1 * c6_2 / (c6_1 + c6_2)
241
+
242
+ # Get r4r2 values for C8 coefficient calculation
243
+ r4r2_1 = self.d4params.get_r4r2(elem1)
244
+ r4r2_2 = self.d4params.get_r4r2(elem2)
245
+
246
+ # Calculate C8 coefficient using D4 formula
247
+ c8_param = 3.0 * c6_param * np.sqrt(r4r2_1 * r4r2_2)
248
+
249
+ # Calculate R0 parameter (vdW radii sum)
250
+ r0_1 = UFF_VDW_distance_lib(elem1) / self.bohr2angstroms
251
+ r0_2 = UFF_VDW_distance_lib(elem2) / self.bohr2angstroms
252
+ r0_param = r0_1 + r0_2
253
+
254
+ # Apply charge scaling (D4-specific feature)
255
+ # Gaussian charge scaling function
256
+ q_scaling = np.exp(-self.d4params.ga * (q1**2 + q2**2))
257
+
258
+ return c6_param, c8_param, r0_param, q_scaling
259
+
260
+ def calc_d4_gradient_components(self, x_ij, y_ij, z_ij, c6_param, c8_param, r0_param, q_scaling=1.0):
261
+ """
262
+ Calculate D4 dispersion gradient components.
263
+
264
+ Args:
265
+ x_ij, y_ij, z_ij: Distance components
266
+ c6_param: C6 dispersion coefficient
267
+ c8_param: C8 dispersion coefficient
268
+ r0_param: van der Waals radius sum parameter
269
+ q_scaling: Charge-dependent scaling factor
270
+
271
+ Returns:
272
+ tuple: (xx, xy, xz, yy, yz, zz) gradient components
273
+ """
274
+ r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
275
+ r_ij = np.sqrt(r_ij_2)
276
+
277
+ if r_ij < 0.1: # Avoid numerical issues
278
+ return 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
279
+
280
+ # BJ damping parameters
281
+ r0_plus_a1 = r0_param + self.d4params.a1
282
+ a2_term = self.d4params.a2
283
+ bj_term_6 = (r0_plus_a1 * a2_term)**6
284
+ bj_term_8 = (r0_plus_a1 * a2_term)**8
285
+
286
+ # Calculate damping functions and their derivatives
287
+ r_ij_6 = r_ij**6
288
+ r_ij_8 = r_ij**8
289
+
290
+ # C6 term: damping and derivatives
291
+ damp_6 = r_ij_6 / (r_ij_6 + bj_term_6)
292
+ d_damp_6_dr = 6.0 * r_ij_6 * bj_term_6 / ((r_ij_6 + bj_term_6)**2 * r_ij)
293
+
294
+ # C8 term: damping and derivatives
295
+ damp_8 = r_ij_8 / (r_ij_8 + bj_term_8)
296
+ d_damp_8_dr = 8.0 * r_ij_8 * bj_term_8 / ((r_ij_8 + bj_term_8)**2 * r_ij)
297
+
298
+ # Apply charge scaling
299
+ c6_scaled = c6_param * q_scaling
300
+ c8_scaled = c8_param * q_scaling
301
+
302
+ # Force (negative derivative of energy)
303
+ f6 = self.d4params.s6 * c6_scaled * (6.0 * damp_6 / r_ij**7 + d_damp_6_dr / r_ij**6)
304
+ f8 = self.d4params.s8 * c8_scaled * (8.0 * damp_8 / r_ij**9 + d_damp_8_dr / r_ij**8)
305
+
306
+ # Total force
307
+ force = f6 + f8
308
+
309
+ # Calculate derivative components
310
+ deriv_scale = force / r_ij
311
+
312
+ # Calculate gradient components
313
+ xx = deriv_scale * x_ij**2 / r_ij_2
314
+ xy = deriv_scale * x_ij * y_ij / r_ij_2
315
+ xz = deriv_scale * x_ij * z_ij / r_ij_2
316
+ yy = deriv_scale * y_ij**2 / r_ij_2
317
+ yz = deriv_scale * y_ij * z_ij / r_ij_2
318
+ zz = deriv_scale * z_ij**2 / r_ij_2
319
+
320
+ return xx, xy, xz, yy, yz, zz
321
+
322
+ def lindh2007_bond(self, coord, element_list, charges, cn):
323
+ """
324
+ Calculate bond stretching contributions to the Hessian.
325
+
326
+ Args:
327
+ coord: Atomic coordinates (Bohr)
328
+ element_list: List of element symbols
329
+ charges: Atomic partial charges
330
+ cn: Coordination numbers
331
+ """
332
+ n_atoms = len(coord)
333
+
334
+ for i in range(n_atoms):
335
+ i_idx = self.select_idx(element_list[i])
336
+
337
+ for j in range(i):
338
+ j_idx = self.select_idx(element_list[j])
339
+
340
+ # Calculate distance components and magnitude
341
+ x_ij = coord[i][0] - coord[j][0]
342
+ y_ij = coord[i][1] - coord[j][1]
343
+ z_ij = coord[i][2] - coord[j][2]
344
+ r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
345
+ r_ij = np.sqrt(r_ij_2)
346
+
347
+ # Get Lindh parameters
348
+ r_0 = self.rAv[i_idx][j_idx]
349
+ d_0 = self.dAv[i_idx][j_idx]
350
+ alpha = self.aAv[i_idx][j_idx]
351
+
352
+ # Determine appropriate bond length based on bond type
353
+ single_bond = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
354
+ covalent_length = single_bond # Default to single bond
355
+
356
+ # Get D4 parameters with charge scaling
357
+ c6_param, c8_param, r0_param, q_scaling = self.get_d4_parameters(
358
+ element_list[i], element_list[j], charges[i], charges[j])
359
+
360
+ # Calculate force constants
361
+ lindh_force = self.kr * self.calc_force_const(alpha, covalent_length, r_ij_2)
362
+
363
+ # Add D4 dispersion if atoms are far apart
364
+ d4_factor = 0.0
365
+ if r_ij > 2.0 * covalent_length:
366
+ d4_factor = self.kd * self.calc_d4_force_const(r_ij, c6_param, c8_param, r0_param, q_scaling)
367
+
368
+ # Combined force constant
369
+ g_mm = lindh_force + d4_factor
370
+
371
+ # Calculate D4 gradient components
372
+ d4_xx, d4_xy, d4_xz, d4_yy, d4_yz, d4_zz = self.calc_d4_gradient_components(
373
+ x_ij, y_ij, z_ij, c6_param, c8_param, r0_param, q_scaling)
374
+
375
+ # Calculate Hessian elements
376
+ hess_xx = g_mm * x_ij**2 / r_ij_2 - d4_xx
377
+ hess_xy = g_mm * x_ij * y_ij / r_ij_2 - d4_xy
378
+ hess_xz = g_mm * x_ij * z_ij / r_ij_2 - d4_xz
379
+ hess_yy = g_mm * y_ij**2 / r_ij_2 - d4_yy
380
+ hess_yz = g_mm * y_ij * z_ij / r_ij_2 - d4_yz
381
+ hess_zz = g_mm * z_ij**2 / r_ij_2 - d4_zz
382
+
383
+ # Update diagonal blocks
384
+ i_offset = i * 3
385
+ j_offset = j * 3
386
+
387
+ # i-i block
388
+ self.cart_hess[i_offset, i_offset] += hess_xx
389
+ self.cart_hess[i_offset + 1, i_offset] += hess_xy
390
+ self.cart_hess[i_offset + 1, i_offset + 1] += hess_yy
391
+ self.cart_hess[i_offset + 2, i_offset] += hess_xz
392
+ self.cart_hess[i_offset + 2, i_offset + 1] += hess_yz
393
+ self.cart_hess[i_offset + 2, i_offset + 2] += hess_zz
394
+
395
+ # j-j block
396
+ self.cart_hess[j_offset, j_offset] += hess_xx
397
+ self.cart_hess[j_offset + 1, j_offset] += hess_xy
398
+ self.cart_hess[j_offset + 1, j_offset + 1] += hess_yy
399
+ self.cart_hess[j_offset + 2, j_offset] += hess_xz
400
+ self.cart_hess[j_offset + 2, j_offset + 1] += hess_yz
401
+ self.cart_hess[j_offset + 2, j_offset + 2] += hess_zz
402
+
403
+ # i-j block
404
+ self.cart_hess[i_offset, j_offset] -= hess_xx
405
+ self.cart_hess[i_offset, j_offset + 1] -= hess_xy
406
+ self.cart_hess[i_offset, j_offset + 2] -= hess_xz
407
+ self.cart_hess[i_offset + 1, j_offset] -= hess_xy
408
+ self.cart_hess[i_offset + 1, j_offset + 1] -= hess_yy
409
+ self.cart_hess[i_offset + 1, j_offset + 2] -= hess_yz
410
+ self.cart_hess[i_offset + 2, j_offset] -= hess_xz
411
+ self.cart_hess[i_offset + 2, j_offset + 1] -= hess_yz
412
+ self.cart_hess[i_offset + 2, j_offset + 2] -= hess_zz
413
+
414
+ def lindh2007_angle(self, coord, element_list, charges, cn):
415
+ """
416
+ Calculate angle bending contributions to the Hessian with D4 dispersion.
417
+
418
+ Args:
419
+ coord: Atomic coordinates (Bohr)
420
+ element_list: List of element symbols
421
+ charges: Atomic partial charges
422
+ cn: Coordination numbers
423
+ """
424
+ n_atoms = len(coord)
425
+
426
+ for i in range(n_atoms):
427
+ i_idx = self.select_idx(element_list[i])
428
+
429
+ for j in range(n_atoms):
430
+ if i == j:
431
+ continue
432
+
433
+ j_idx = self.select_idx(element_list[j])
434
+
435
+ # Vector from j to i
436
+ x_ij = coord[i][0] - coord[j][0]
437
+ y_ij = coord[i][1] - coord[j][1]
438
+ z_ij = coord[i][2] - coord[j][2]
439
+ r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
440
+ r_ij = np.sqrt(r_ij_2)
441
+
442
+ # Get bond parameters
443
+ covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
444
+
445
+ # Get Lindh parameters
446
+ r_ij_0 = self.rAv[i_idx][j_idx]
447
+ d_ij_0 = self.dAv[i_idx][j_idx]
448
+ alpha_ij = self.aAv[i_idx][j_idx]
449
+
450
+ # Loop through potential third atoms to form an angle
451
+ for k in range(j):
452
+ if i == k:
453
+ continue
454
+
455
+ k_idx = self.select_idx(element_list[k])
456
+
457
+ # Get parameters for i-k interaction
458
+ r_ik_0 = self.rAv[i_idx][k_idx]
459
+ d_ik_0 = self.dAv[i_idx][k_idx]
460
+ alpha_ik = self.aAv[i_idx][k_idx]
461
+
462
+ # Vector from k to i
463
+ x_ik = coord[i][0] - coord[k][0]
464
+ y_ik = coord[i][1] - coord[k][1]
465
+ z_ik = coord[i][2] - coord[k][2]
466
+ r_ik_2 = x_ik**2 + y_ik**2 + z_ik**2
467
+ r_ik = np.sqrt(r_ik_2)
468
+
469
+ # Get bond parameters
470
+ covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
471
+
472
+ # Check if angle is well-defined (not linear)
473
+ cos_angle = (x_ij * x_ik + y_ij * y_ik + z_ij * z_ik) / (r_ij * r_ik)
474
+
475
+ if abs(cos_angle - 1.0) < self.eps:
476
+ continue # Skip near-linear angles
477
+
478
+ # Vector from k to j
479
+ x_jk = coord[j][0] - coord[k][0]
480
+ y_jk = coord[j][1] - coord[k][1]
481
+ z_jk = coord[j][2] - coord[k][2]
482
+ r_jk_2 = x_jk**2 + y_jk**2 + z_jk**2
483
+ r_jk = np.sqrt(r_jk_2)
484
+
485
+ # Calculate force constants with D4 contributions
486
+ c6_ij, c8_ij, r0_ij, q_ij = self.get_d4_parameters(
487
+ element_list[i], element_list[j], charges[i], charges[j])
488
+ c6_ik, c8_ik, r0_ik, q_ik = self.get_d4_parameters(
489
+ element_list[i], element_list[k], charges[i], charges[k])
490
+
491
+ g_ij = self.calc_force_const(alpha_ij, covalent_length_ij, r_ij_2)
492
+ if r_ij > 2.0 * covalent_length_ij:
493
+ g_ij += 0.5 * self.kd * self.calc_d4_force_const(r_ij, c6_ij, c8_ij, r0_ij, q_ij)
494
+
495
+ g_ik = self.calc_force_const(alpha_ik, covalent_length_ik, r_ik_2)
496
+ if r_ik > 2.0 * covalent_length_ik:
497
+ g_ik += 0.5 * self.kd * self.calc_d4_force_const(r_ik, c6_ik, c8_ik, r0_ik, q_ik)
498
+
499
+ # Angular force constant
500
+ g_jk = self.kf * (g_ij + 0.5 * self.kd / self.kr * d_ij_0) * (g_ik + 0.5 * self.kd / self.kr * d_ik_0)
501
+
502
+ # Cross product magnitude for sin(theta)
503
+ r_cross_2 = (y_ij * z_ik - z_ij * y_ik)**2 + (z_ij * x_ik - x_ij * z_ik)**2 + (x_ij * y_ik - y_ij * x_ik)**2
504
+ r_cross = np.sqrt(r_cross_2) if r_cross_2 > 1.0e-12 else 0.0
505
+
506
+ # Skip if distances are too small
507
+ if r_ik <= self.eps or r_ij <= self.eps or r_jk <= self.eps:
508
+ continue
509
+
510
+ # Calculate angle and its derivatives
511
+ dot_product = x_ij * x_ik + y_ij * y_ik + z_ij * z_ik
512
+ cos_theta = dot_product / (r_ij * r_ik)
513
+ sin_theta = r_cross / (r_ij * r_ik)
514
+
515
+ if sin_theta > self.eps: # Non-linear case
516
+ # Calculate derivatives
517
+ s_xj = (x_ij / r_ij * cos_theta - x_ik / r_ik) / (r_ij * sin_theta)
518
+ s_yj = (y_ij / r_ij * cos_theta - y_ik / r_ik) / (r_ij * sin_theta)
519
+ s_zj = (z_ij / r_ij * cos_theta - z_ik / r_ik) / (r_ij * sin_theta)
520
+
521
+ s_xk = (x_ik / r_ik * cos_theta - x_ij / r_ij) / (r_ik * sin_theta)
522
+ s_yk = (y_ik / r_ik * cos_theta - y_ij / r_ij) / (r_ik * sin_theta)
523
+ s_zk = (z_ik / r_ik * cos_theta - z_ij / r_ij) / (r_ik * sin_theta)
524
+
525
+ s_xi = -s_xj - s_xk
526
+ s_yi = -s_yj - s_yk
527
+ s_zi = -s_zj - s_zk
528
+
529
+ s_i = [s_xi, s_yi, s_zi]
530
+ s_j = [s_xj, s_yj, s_zj]
531
+ s_k = [s_xk, s_yk, s_zk]
532
+
533
+ # Update Hessian elements
534
+ for l in range(3):
535
+ for m in range(3):
536
+ # i-j block
537
+ if i > j:
538
+ self.cart_hess[i*3+l, j*3+m] += g_jk * s_i[l] * s_j[m]
539
+ else:
540
+ self.cart_hess[j*3+l, i*3+m] += g_jk * s_j[l] * s_i[m]
541
+
542
+ # i-k block
543
+ if i > k:
544
+ self.cart_hess[i*3+l, k*3+m] += g_jk * s_i[l] * s_k[m]
545
+ else:
546
+ self.cart_hess[k*3+l, i*3+m] += g_jk * s_k[l] * s_i[m]
547
+
548
+ # j-k block
549
+ if j > k:
550
+ self.cart_hess[j*3+l, k*3+m] += g_jk * s_j[l] * s_k[m]
551
+ else:
552
+ self.cart_hess[k*3+l, j*3+m] += g_jk * s_k[l] * s_j[m]
553
+
554
+ # Diagonal blocks
555
+ for l in range(3):
556
+ for m in range(l):
557
+ self.cart_hess[j*3+l, j*3+m] += g_jk * s_j[l] * s_j[m]
558
+ self.cart_hess[i*3+l, i*3+m] += g_jk * s_i[l] * s_i[m]
559
+ self.cart_hess[k*3+l, k*3+m] += g_jk * s_k[l] * s_k[m]
560
+
561
+ else: # Linear case
562
+ # Handle linear angles using arbitrary perpendicular vectors
563
+ if abs(y_ij) < self.eps and abs(z_ij) < self.eps:
564
+ x_1, y_1, z_1 = -y_ij, x_ij, 0.0
565
+ x_2, y_2, z_2 = -x_ij * z_ij, -y_ij * z_ij, x_ij**2 + y_ij**2
566
+ else:
567
+ x_1, y_1, z_1 = 1.0, 0.0, 0.0
568
+ x_2, y_2, z_2 = 0.0, 1.0, 0.0
569
+
570
+ x = [x_1, x_2]
571
+ y = [y_1, y_2]
572
+ z = [z_1, z_2]
573
+
574
+ # Calculate derivatives for two perpendicular directions
575
+ for ii in range(2):
576
+ r_1 = np.sqrt(x[ii]**2 + y[ii]**2 + z[ii]**2)
577
+ cos_theta_x = x[ii] / r_1
578
+ cos_theta_y = y[ii] / r_1
579
+ cos_theta_z = z[ii] / r_1
580
+
581
+ # Derivatives
582
+ s_xj = -cos_theta_x / r_ij
583
+ s_yj = -cos_theta_y / r_ij
584
+ s_zj = -cos_theta_z / r_ij
585
+ s_xk = -cos_theta_x / r_ik
586
+ s_yk = -cos_theta_y / r_ik
587
+ s_zk = -cos_theta_z / r_ik
588
+
589
+ s_xi = -s_xj - s_xk
590
+ s_yi = -s_yj - s_yk
591
+ s_zi = -s_zj - s_zk
592
+
593
+ s_i = [s_xi, s_yi, s_zi]
594
+ s_j = [s_xj, s_yj, s_zj]
595
+ s_k = [s_xk, s_yk, s_zk]
596
+
597
+ # Update Hessian elements
598
+ for l in range(3):
599
+ for m in range(3):
600
+ # i-j block
601
+ if i > j:
602
+ self.cart_hess[i*3+l, j*3+m] += g_jk * s_i[l] * s_j[m]
603
+ else:
604
+ self.cart_hess[j*3+l, i*3+m] += g_jk * s_j[l] * s_i[m]
605
+
606
+ # i-k block
607
+ if i > k:
608
+ self.cart_hess[i*3+l, k*3+m] += g_jk * s_i[l] * s_k[m]
609
+ else:
610
+ self.cart_hess[k*3+l, i*3+m] += g_jk * s_k[l] * s_i[m]
611
+
612
+ # j-k block
613
+ if j > k:
614
+ self.cart_hess[j*3+l, k*3+m] += g_jk * s_j[l] * s_k[m]
615
+ else:
616
+ self.cart_hess[k*3+l, j*3+m] += g_jk * s_k[l] * s_j[m]
617
+
618
+ # Diagonal blocks
619
+ for l in range(3):
620
+ for m in range(l):
621
+ self.cart_hess[j*3+l, j*3+m] += g_jk * s_j[l] * s_j[m]
622
+ self.cart_hess[i*3+l, i*3+m] += g_jk * s_i[l] * s_i[m]
623
+ self.cart_hess[k*3+l, k*3+m] += g_jk * s_k[l] * s_k[m]
624
+
625
+ def lindh2007_dihedral_angle(self, coord, element_list, charges, cn):
626
+ """
627
+ Calculate dihedral angle (torsion) contributions to the Hessian with D4 dispersion.
628
+
629
+ Args:
630
+ coord: Atomic coordinates (Bohr)
631
+ element_list: List of element symbols
632
+ charges: Atomic partial charges
633
+ cn: Coordination numbers
634
+ """
635
+ n_atoms = len(coord)
636
+
637
+ for j in range(n_atoms):
638
+ t_xyz_2 = coord[j]
639
+
640
+ for k in range(j+1, n_atoms):
641
+ t_xyz_3 = coord[k]
642
+
643
+ for i in range(j):
644
+ if i == k:
645
+ continue
646
+
647
+ t_xyz_1 = coord[i]
648
+
649
+ for l in range(k+1, n_atoms):
650
+ if l == i or l == j:
651
+ continue
652
+
653
+ t_xyz_4 = coord[l]
654
+
655
+ # Get element indices for parameter lookup
656
+ i_idx = self.select_idx(element_list[i])
657
+ j_idx = self.select_idx(element_list[j])
658
+ k_idx = self.select_idx(element_list[k])
659
+ l_idx = self.select_idx(element_list[l])
660
+
661
+ # Get Lindh parameters
662
+ r_ij_0 = self.rAv[i_idx][j_idx]
663
+ d_ij_0 = self.dAv[i_idx][j_idx]
664
+ alpha_ij = self.aAv[i_idx][j_idx]
665
+
666
+ r_jk_0 = self.rAv[j_idx][k_idx]
667
+ d_jk_0 = self.dAv[j_idx][k_idx]
668
+ alpha_jk = self.aAv[j_idx][k_idx]
669
+
670
+ r_kl_0 = self.rAv[k_idx][l_idx]
671
+ d_kl_0 = self.dAv[k_idx][l_idx]
672
+ alpha_kl = self.aAv[k_idx][l_idx]
673
+
674
+ # Calculate bond vectors and lengths
675
+ r_ij = coord[i] - coord[j]
676
+ r_jk = coord[j] - coord[k]
677
+ r_kl = coord[k] - coord[l]
678
+
679
+ # Get bond parameters
680
+ covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
681
+ covalent_length_jk = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
682
+ covalent_length_kl = covalent_radii_lib(element_list[k]) + covalent_radii_lib(element_list[l])
683
+
684
+ # Calculate squared distances
685
+ r_ij_2 = np.sum(r_ij**2)
686
+ r_jk_2 = np.sum(r_jk**2)
687
+ r_kl_2 = np.sum(r_kl**2)
688
+
689
+ # Calculate norms
690
+ norm_r_ij = np.sqrt(r_ij_2)
691
+ norm_r_jk = np.sqrt(r_jk_2)
692
+ norm_r_kl = np.sqrt(r_kl_2)
693
+
694
+ # Check if near-linear angles would cause numerical issues
695
+ a35 = (35.0/180) * np.pi
696
+ cosfi_max = np.cos(a35)
697
+
698
+ cosfi2 = np.dot(r_ij, r_jk) / np.sqrt(r_ij_2 * r_jk_2)
699
+ if abs(cosfi2) > cosfi_max:
700
+ continue
701
+
702
+ cosfi3 = np.dot(r_kl, r_jk) / np.sqrt(r_kl_2 * r_jk_2)
703
+ if abs(cosfi3) > cosfi_max:
704
+ continue
705
+
706
+ # Get D4 parameters for bond pairs with charge scaling
707
+ c6_ij, c8_ij, r0_ij, q_ij = self.get_d4_parameters(
708
+ element_list[i], element_list[j], charges[i], charges[j])
709
+ c6_jk, c8_jk, r0_jk, q_jk = self.get_d4_parameters(
710
+ element_list[j], element_list[k], charges[j], charges[k])
711
+ c6_kl, c8_kl, r0_kl, q_kl = self.get_d4_parameters(
712
+ element_list[k], element_list[l], charges[k], charges[l])
713
+
714
+ # Calculate force constants with D4 contributions
715
+ g_ij = self.calc_force_const(alpha_ij, covalent_length_ij, r_ij_2)
716
+ if norm_r_ij > 2.0 * covalent_length_ij:
717
+ g_ij += 0.5 * self.kd * self.calc_d4_force_const(
718
+ norm_r_ij, c6_ij, c8_ij, r0_ij, q_ij)
719
+
720
+ g_jk = self.calc_force_const(alpha_jk, covalent_length_jk, r_jk_2)
721
+ if norm_r_jk > 2.0 * covalent_length_jk:
722
+ g_jk += 0.5 * self.kd * self.calc_d4_force_const(
723
+ norm_r_jk, c6_jk, c8_jk, r0_jk, q_jk)
724
+
725
+ g_kl = self.calc_force_const(alpha_kl, covalent_length_kl, r_kl_2)
726
+ if norm_r_kl > 2.0 * covalent_length_kl:
727
+ g_kl += 0.5 * self.kd * self.calc_d4_force_const(
728
+ norm_r_kl, c6_kl, c8_kl, r0_kl, q_kl)
729
+
730
+ # Calculate torsion force constant
731
+ t_ij = self.kt * (g_ij * 0.5 * self.kd / self.kr * d_ij_0) * \
732
+ (g_jk * 0.5 * self.kd / self.kr * d_jk_0) * \
733
+ (g_kl * 0.5 * self.kd / self.kr * d_kl_0)
734
+
735
+ # Calculate torsion angle and derivatives
736
+ t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
737
+ tau, c = torsion2(t_xyz)
738
+
739
+ # Extract derivatives
740
+ s_i = c[0]
741
+ s_j = c[1]
742
+ s_k = c[2]
743
+ s_l = c[3]
744
+
745
+ # Apply coordination number scaling (D4-specific)
746
+ # Torsions involving sp3 centers get higher weight
747
+ cn_scaling = 1.0
748
+ if 3.9 <= cn[j] <= 4.1 and 3.9 <= cn[k] <= 4.1: # Both sp3
749
+ cn_scaling = 1.2
750
+ elif (cn[j] <= 3.1 and cn[k] <= 3.1): # Both sp2 or lower
751
+ cn_scaling = 0.8
752
+
753
+ # Apply scaling to force constant
754
+ t_ij *= cn_scaling
755
+
756
+ # Update off-diagonal blocks
757
+ for n in range(3):
758
+ for m in range(3):
759
+ self.cart_hess[3*i+n, 3*j+m] += t_ij * s_i[n] * s_j[m]
760
+ self.cart_hess[3*i+n, 3*k+m] += t_ij * s_i[n] * s_k[m]
761
+ self.cart_hess[3*i+n, 3*l+m] += t_ij * s_i[n] * s_l[m]
762
+ self.cart_hess[3*j+n, 3*k+m] += t_ij * s_j[n] * s_k[m]
763
+ self.cart_hess[3*j+n, 3*l+m] += t_ij * s_j[n] * s_l[m]
764
+ self.cart_hess[3*k+n, 3*l+m] += t_ij * s_k[n] * s_l[m]
765
+
766
+ # Update diagonal blocks (lower triangle)
767
+ for n in range(3):
768
+ for m in range(n):
769
+ self.cart_hess[3*i+n, 3*i+m] += t_ij * s_i[n] * s_i[m]
770
+ self.cart_hess[3*j+n, 3*j+m] += t_ij * s_j[n] * s_j[m]
771
+ self.cart_hess[3*k+n, 3*k+m] += t_ij * s_k[n] * s_k[m]
772
+ self.cart_hess[3*l+n, 3*l+m] += t_ij * s_l[n] * s_l[m]
773
+
774
+ def lindh2007_out_of_plane(self, coord, element_list, charges, cn):
775
+ """
776
+ Calculate out-of-plane bending contributions to the Hessian with D4 dispersion.
777
+
778
+ Args:
779
+ coord: Atomic coordinates (Bohr)
780
+ element_list: List of element symbols
781
+ charges: Atomic partial charges
782
+ cn: Coordination numbers
783
+ """
784
+ n_atoms = len(coord)
785
+
786
+ for i in range(n_atoms):
787
+ t_xyz_4 = coord[i]
788
+
789
+ for j in range(i+1, n_atoms):
790
+ t_xyz_1 = coord[j]
791
+
792
+ for k in range(j+1, n_atoms):
793
+ t_xyz_2 = coord[k]
794
+
795
+ for l in range(k+1, n_atoms):
796
+ t_xyz_3 = coord[l]
797
+
798
+ # Calculate bond vectors
799
+ r_ij = coord[i] - coord[j]
800
+ r_ik = coord[i] - coord[k]
801
+ r_il = coord[i] - coord[l]
802
+
803
+ # Get bond parameters
804
+ covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
805
+ covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
806
+ covalent_length_il = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[l])
807
+
808
+ # Get element indices for parameter lookup
809
+ idx_i = self.select_idx(element_list[i])
810
+ idx_j = self.select_idx(element_list[j])
811
+ idx_k = self.select_idx(element_list[k])
812
+ idx_l = self.select_idx(element_list[l])
813
+
814
+ # Get Lindh parameters
815
+ d_ij_0 = self.dAv[idx_i][idx_j]
816
+ r_ij_0 = self.rAv[idx_i][idx_j]
817
+ alpha_ij = self.aAv[idx_i][idx_j]
818
+
819
+ d_ik_0 = self.dAv[idx_i][idx_k]
820
+ r_ik_0 = self.rAv[idx_i][idx_k]
821
+ alpha_ik = self.aAv[idx_i][idx_k]
822
+
823
+ d_il_0 = self.dAv[idx_i][idx_l]
824
+ r_il_0 = self.rAv[idx_i][idx_l]
825
+ alpha_il = self.aAv[idx_i][idx_l]
826
+
827
+ # Calculate squared distances
828
+ r_ij_2 = np.sum(r_ij**2)
829
+ r_ik_2 = np.sum(r_ik**2)
830
+ r_il_2 = np.sum(r_il**2)
831
+
832
+ # Calculate norms
833
+ norm_r_ij = np.sqrt(r_ij_2)
834
+ norm_r_ik = np.sqrt(r_ik_2)
835
+ norm_r_il = np.sqrt(r_il_2)
836
+
837
+ # Check for near-linear angles that would cause numerical issues
838
+ cosfi2 = np.dot(r_ij, r_ik) / (norm_r_ij * norm_r_ik)
839
+ if abs(abs(cosfi2) - 1.0) < 1.0e-1:
840
+ continue
841
+
842
+ cosfi3 = np.dot(r_ij, r_il) / (norm_r_ij * norm_r_il)
843
+ if abs(abs(cosfi3) - 1.0) < 1.0e-1:
844
+ continue
845
+
846
+ cosfi4 = np.dot(r_ik, r_il) / (norm_r_ik * norm_r_il)
847
+ if abs(abs(cosfi4) - 1.0) < 1.0e-1:
848
+ continue
849
+
850
+ # Get D4 parameters for each pair with charge scaling
851
+ c6_ij, c8_ij, r0_ij, q_ij = self.get_d4_parameters(
852
+ element_list[i], element_list[j], charges[i], charges[j])
853
+ c6_ik, c8_ik, r0_ik, q_ik = self.get_d4_parameters(
854
+ element_list[i], element_list[k], charges[i], charges[k])
855
+ c6_il, c8_il, r0_il, q_il = self.get_d4_parameters(
856
+ element_list[i], element_list[l], charges[i], charges[l])
857
+
858
+ # Disable direct D4 contributions to out-of-plane terms
859
+ kd = 0.0
860
+
861
+ # Calculate force constants for each bond
862
+ g_ij = self.calc_force_const(alpha_ij, covalent_length_ij, r_ij_2)
863
+ if norm_r_ij > 2.0 * covalent_length_ij:
864
+ g_ij += 0.5 * kd * self.calc_d4_force_const(
865
+ norm_r_ij, c6_ij, c8_ij, r0_ij, q_ij)
866
+
867
+ g_ik = self.calc_force_const(alpha_ik, covalent_length_ik, r_ik_2)
868
+ if norm_r_ik > 2.0 * covalent_length_ik:
869
+ g_ik += 0.5 * kd * self.calc_d4_force_const(
870
+ norm_r_ik, c6_ik, c8_ik, r0_ik, q_ik)
871
+
872
+ g_il = self.calc_force_const(alpha_il, covalent_length_il, r_il_2)
873
+ if norm_r_il > 2.0 * covalent_length_il:
874
+ g_il += 0.5 * kd * self.calc_d4_force_const(
875
+ norm_r_il, c6_il, c8_il, r0_il, q_il)
876
+
877
+ # Combined force constant for out-of-plane motion
878
+ t_ij = self.ko * g_ij * g_ik * g_il
879
+
880
+ # Apply special treatment for planar centers (D4 enhancement)
881
+ if 2.9 <= cn[i] <= 3.1: # Planar center (CN=3 typical for sp2)
882
+ t_ij *= 1.2 # Increase force constant for planar centers
883
+
884
+ # Calculate out-of-plane angle and derivatives
885
+ t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
886
+ theta, c = outofplane2(t_xyz)
887
+
888
+ # Extract derivatives
889
+ s_i = c[0]
890
+ s_j = c[1]
891
+ s_k = c[2]
892
+ s_l = c[3]
893
+
894
+ # Update off-diagonal blocks
895
+ for n in range(3):
896
+ for m in range(3):
897
+ self.cart_hess[i*3+n, j*3+m] += t_ij * s_i[n] * s_j[m]
898
+ self.cart_hess[i*3+n, k*3+m] += t_ij * s_i[n] * s_k[m]
899
+ self.cart_hess[i*3+n, l*3+m] += t_ij * s_i[n] * s_l[m]
900
+ self.cart_hess[j*3+n, k*3+m] += t_ij * s_j[n] * s_k[m]
901
+ self.cart_hess[j*3+n, l*3+m] += t_ij * s_j[n] * s_l[m]
902
+ self.cart_hess[k*3+n, l*3+m] += t_ij * s_k[n] * s_l[m]
903
+
904
+ # Update diagonal blocks (lower triangle)
905
+ for n in range(3):
906
+ for m in range(n):
907
+ self.cart_hess[i*3+n, i*3+m] += t_ij * s_i[n] * s_i[m]
908
+ self.cart_hess[j*3+n, j*3+m] += t_ij * s_j[n] * s_j[m]
909
+ self.cart_hess[k*3+n, k*3+m] += t_ij * s_k[n] * s_k[m]
910
+ self.cart_hess[l*3+n, l*3+m] += t_ij * s_l[n] * s_l[m]
911
+
912
+ def calculate_three_body_terms(self, coord, element_list, charges, cn):
913
+ """
914
+ Calculate D4-specific three-body dispersion contributions.
915
+
916
+ Args:
917
+ coord: Atomic coordinates (Bohr)
918
+ element_list: List of element symbols
919
+ charges: Atomic partial charges
920
+ cn: Coordination numbers
921
+ """
922
+ n_atoms = len(coord)
923
+
924
+ # Only apply three-body terms for larger systems to improve efficiency
925
+ if n_atoms < 3:
926
+ return
927
+
928
+ # Apply D4 three-body terms
929
+ # This is a simplified implementation for Hessian calculations
930
+ for i in range(n_atoms):
931
+ for j in range(i+1, n_atoms):
932
+ r_ij = np.linalg.norm(coord[i] - coord[j])
933
+
934
+ # Skip if atoms are too close (likely bonded)
935
+ cov_cutoff = (covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])) * 1.5
936
+ if r_ij < cov_cutoff:
937
+ continue
938
+
939
+ for k in range(j+1, n_atoms):
940
+ r_ik = np.linalg.norm(coord[i] - coord[k])
941
+ r_jk = np.linalg.norm(coord[j] - coord[k])
942
+
943
+ # Skip if atoms are too close (likely bonded)
944
+ if r_ik < (covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])) * 1.5:
945
+ continue
946
+ if r_jk < (covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])) * 1.5:
947
+ continue
948
+
949
+ # Get D4 parameters
950
+ c6_ij, c8_ij, r0_ij, q_ij = self.get_d4_parameters(
951
+ element_list[i], element_list[j], charges[i], charges[j])
952
+ c6_ik, c8_ik, r0_ik, q_ik = self.get_d4_parameters(
953
+ element_list[i], element_list[k], charges[i], charges[k])
954
+ c6_jk, c8_jk, r0_jk, q_jk = self.get_d4_parameters(
955
+ element_list[j], element_list[k], charges[j], charges[k])
956
+
957
+ # Calculate C9 coefficient for three-body term (Axilrod-Teller-Muto)
958
+ c9_ijk = np.sqrt(c6_ij * c6_ik * c6_jk)
959
+
960
+ # Apply charge scaling
961
+ q_scaling = np.exp(-self.d4params.gc * (charges[i]**2 + charges[j]**2 + charges[k]**2))
962
+
963
+ # Skip for very long-range interactions (r > 15 Bohr)
964
+ if r_ij > 15.0 or r_ik > 15.0 or r_jk > 15.0:
965
+ continue
966
+
967
+ # Calculate three-body term (simplified for Hessian implementation)
968
+ # Note: This is a very simplified approximation
969
+ threebody_scale = 0.002 * self.d4params.s9 * q_scaling * c9_ijk / (r_ij * r_ik * r_jk)**3
970
+
971
+ # Add minimal contribution to Hessian - just main diagonal elements
972
+ # For proper implementation, full derivatives would be needed
973
+ for idx in [i, j, k]:
974
+ for n in range(3):
975
+ self.cart_hess[idx*3+n, idx*3+n] += threebody_scale
976
+
977
+ def main(self, coord, element_list, cart_gradient):
978
+ """
979
+ Calculate approximate Hessian using Lindh's 2007 model with D4 dispersion.
980
+
981
+ Args:
982
+ coord: Atomic coordinates (Bohr)
983
+ element_list: List of element symbols
984
+ cart_gradient: Cartesian gradient vector
985
+
986
+ Returns:
987
+ hess_proj: Projected approximate Hessian matrix
988
+ """
989
+ print("Generating Lindh's (2007) approximate Hessian with D4 dispersion...")
990
+
991
+ # Scale eigenvalues based on gradient norm (smaller scale for larger gradients)
992
+ norm_grad = np.linalg.norm(cart_gradient)
993
+ scale = 0.1
994
+ eigval_scale = scale * np.exp(-1 * norm_grad**2.0)
995
+
996
+ # Initialize Hessian matrix
997
+ n_atoms = len(coord)
998
+ self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
999
+
1000
+ # Calculate atomic charges for D4 scaling
1001
+ charges = self.estimate_atomic_charges(coord, element_list)
1002
+
1003
+ # Calculate coordination numbers for D4 scaling
1004
+ cn = self.calculate_coordination_numbers(coord, element_list)
1005
+
1006
+ # Calculate individual contributions
1007
+ self.lindh2007_bond(coord, element_list, charges, cn)
1008
+ self.lindh2007_angle(coord, element_list, charges, cn)
1009
+ self.lindh2007_dihedral_angle(coord, element_list, charges, cn)
1010
+ self.lindh2007_out_of_plane(coord, element_list, charges, cn)
1011
+
1012
+ # Add D4-specific three-body terms
1013
+ self.calculate_three_body_terms(coord, element_list, charges, cn)
1014
+
1015
+ # Symmetrize the Hessian matrix
1016
+ for i in range(n_atoms*3):
1017
+ for j in range(i):
1018
+ if abs(self.cart_hess[i, j]) < 1.0e-10:
1019
+ self.cart_hess[i, j] = self.cart_hess[j, i]
1020
+ else:
1021
+ self.cart_hess[j, i] = self.cart_hess[i, j]
1022
+
1023
+ # Project out translational and rotational degrees of freedom
1024
+ hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
1025
+
1026
+ # Adjust eigenvalues for stability based on gradient magnitude
1027
+ eigenvalues, eigenvectors = np.linalg.eigh(hess_proj)
1028
+ hess_proj = np.dot(np.dot(eigenvectors, np.diag(np.abs(eigenvalues) * eigval_scale)), np.transpose(eigenvectors))
1029
+
1030
+ return hess_proj