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,706 @@
1
+ import numpy as np
2
+
3
+ from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib, triple_covalent_radii_lib
4
+ from multioptpy.Utils.calc_tools import Calculationtools
5
+ from multioptpy.ModelHessian.calc_params import torsion2, outofplane2
6
+ from multioptpy.Parameters.parameter import UFF_VDW_distance_lib, D3Parameters
7
+
8
+ class SwartD3ApproxHessian:
9
+ def __init__(self):
10
+ #Swart's Model Hessian augmented with D3 dispersion
11
+ #ref.: M. Swart, F. M. Bickelhaupt, Int. J. Quantum Chem., 2006, 106, 2536–2544.
12
+ #ref.: S. Grimme, J. Antony, S. Ehrlich, H. Krieg, J. Chem. Phys., 2010, 132, 154104
13
+ self.bohr2angstroms = UnitValueLib().bohr2angstroms
14
+ self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
15
+ self.kd = 2.00
16
+
17
+ self.kr = 0.35
18
+ self.kf = 0.15
19
+ self.kt = 0.005
20
+
21
+ self.cutoff = 70.0
22
+ self.eps = 1.0e-12
23
+
24
+ # D3 parameters
25
+ self.d3_params = D3Parameters()
26
+
27
+ # Cyano group parameters
28
+ self.cn_kr = 0.70 # Enhanced force constant for C≡N triple bond
29
+ self.cn_kf = 0.20 # Enhanced force constant for angles involving C≡N
30
+ self.cn_kt = 0.002 # Reduced force constant for torsions involving C≡N
31
+ return
32
+
33
+ def detect_cyano_groups(self, coord, element_list):
34
+ """Detect C≡N triple bonds in the structure"""
35
+ cyano_atoms = [] # List of (C_idx, N_idx) tuples
36
+
37
+ for i in range(len(coord)):
38
+ if element_list[i] != 'C':
39
+ continue
40
+
41
+ for j in range(len(coord)):
42
+ if i == j or element_list[j] != 'N':
43
+ continue
44
+
45
+ # Calculate distance between C and N
46
+ x_ij = coord[i][0] - coord[j][0]
47
+ y_ij = coord[i][1] - coord[j][1]
48
+ z_ij = coord[i][2] - coord[j][2]
49
+ r_ij = np.sqrt(x_ij**2 + y_ij**2 + z_ij**2)
50
+
51
+ # Check if distance is close to a triple bond length
52
+ cn_triple_bond = triple_covalent_radii_lib('C') + triple_covalent_radii_lib('N')
53
+
54
+ if abs(r_ij - cn_triple_bond) < 0.3: # Within 0.3 bohr of ideal length
55
+ # Check if C is connected to only one other atom (besides N)
56
+ connections_to_c = 0
57
+ for k in range(len(coord)):
58
+ if k == i or k == j:
59
+ continue
60
+
61
+ x_ik = coord[i][0] - coord[k][0]
62
+ y_ik = coord[i][1] - coord[k][1]
63
+ z_ik = coord[i][2] - coord[k][2]
64
+ r_ik = np.sqrt(x_ik**2 + y_ik**2 + z_ik**2)
65
+
66
+ cov_dist = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
67
+ if r_ik < 1.3 * cov_dist: # Using 1.3 as a factor to account for bond length variations
68
+ connections_to_c += 1
69
+
70
+ # If C has only one other connection, it's likely a terminal cyano group
71
+ if connections_to_c <= 1:
72
+ cyano_atoms.append((i, j))
73
+
74
+ return cyano_atoms
75
+
76
+ def calc_force_const(self, alpha, covalent_length, distance):
77
+ force_const = np.exp(-1 * alpha * (distance / covalent_length - 1.0))
78
+ return force_const
79
+
80
+ def d3_damping_function(self, r_ij, r0, order=6):
81
+ """D3 rational damping function"""
82
+ a1 = self.d3_params.a1
83
+ a2 = self.d3_params.a2
84
+
85
+ if order == 6:
86
+ return 1.0 / (1.0 + 6.0 * (r_ij / (a1 * r0)) ** a2)
87
+ elif order == 8:
88
+ return 1.0 / (1.0 + 6.0 * (r_ij / (a2 * r0)) ** a1)
89
+ return 0.0
90
+
91
+ def get_d3_parameters(self, elem1, elem2):
92
+ """Get D3 parameters for a pair of elements"""
93
+ # Get R4/R2 values
94
+ r4r2_1 = self.d3_params.get_r4r2(elem1)
95
+ r4r2_2 = self.d3_params.get_r4r2(elem2)
96
+
97
+ # C6 coefficients based on averaging rules
98
+ c6_1 = self.d3_params.get_r4r2(elem1) ** 2
99
+ c6_2 = self.d3_params.get_r4r2(elem2) ** 2
100
+ c6_param = np.sqrt(c6_1 * c6_2)
101
+
102
+ # C8 coefficients using r^4/r^2 ratio
103
+ c8_param = 3.0 * c6_param * np.sqrt(r4r2_1 * r4r2_2)
104
+
105
+ # r0 parameter (combined vdW radii)
106
+ r0_param = np.sqrt(UFF_VDW_distance_lib(elem1) * UFF_VDW_distance_lib(elem2))
107
+
108
+ return c6_param, c8_param, r0_param
109
+
110
+ def calc_d3_force_const(self, r_ij, c6_param, c8_param, r0_param):
111
+ """Calculate D3 dispersion force constant"""
112
+ s6 = self.d3_params.s6
113
+ s8 = self.d3_params.s8
114
+
115
+ # Apply damping functions
116
+ damp6 = self.d3_damping_function(r_ij, r0_param, order=6)
117
+ damp8 = self.d3_damping_function(r_ij, r0_param, order=8)
118
+
119
+ # Energy terms (negative because dispersion is attractive)
120
+ e6 = -s6 * c6_param / r_ij ** 6 * damp6
121
+ e8 = -s8 * c8_param / r_ij ** 8 * damp8
122
+
123
+ # Force constant is the second derivative of energy
124
+ # Simplified approximation of second derivatives
125
+ fc6 = s6 * c6_param * (42.0 / r_ij ** 8) * damp6
126
+ fc8 = s8 * c8_param * (72.0 / r_ij ** 10) * damp8
127
+
128
+ return fc6 + fc8
129
+
130
+ def swart_bond(self, coord, element_list):
131
+ # Detect cyano groups
132
+ cyano_atoms = self.detect_cyano_groups(coord, element_list)
133
+ cyano_set = set()
134
+ for c_idx, n_idx in cyano_atoms:
135
+ cyano_set.add(c_idx)
136
+ cyano_set.add(n_idx)
137
+
138
+ for i in range(len(coord)):
139
+ for j in range(i):
140
+
141
+ x_ij = coord[i][0] - coord[j][0]
142
+ y_ij = coord[i][1] - coord[j][1]
143
+ z_ij = coord[i][2] - coord[j][2]
144
+ r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
145
+ r_ij = np.sqrt(r_ij_2)
146
+ covalent_length = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
147
+
148
+ # Get D3 parameters
149
+ c6_param, c8_param, r0_param = self.get_d3_parameters(element_list[i], element_list[j])
150
+
151
+ # Calculate D3 dispersion contribution
152
+ d3_force_const = self.calc_d3_force_const(r_ij, c6_param, c8_param, r0_param)
153
+
154
+ # Check if this is a cyano bond
155
+ is_cyano_bond = False
156
+ for c_idx, n_idx in cyano_atoms:
157
+ if (i == c_idx and j == n_idx) or (i == n_idx and j == c_idx):
158
+ is_cyano_bond = True
159
+ break
160
+
161
+ # Apply appropriate force constant
162
+ if is_cyano_bond:
163
+ # Special force constant for C≡N triple bond
164
+ g_mm = self.cn_kr * self.calc_force_const(1.0, covalent_length, r_ij) + self.kd * d3_force_const
165
+ else:
166
+ # Regular Swart force constant with D3 dispersion
167
+ g_mm = self.kr * self.calc_force_const(1.0, covalent_length, r_ij) + self.kd * d3_force_const
168
+
169
+ # Calculate Hessian components
170
+ hess_xx = g_mm * x_ij ** 2 / r_ij_2
171
+ hess_xy = g_mm * x_ij * y_ij / r_ij_2
172
+ hess_xz = g_mm * x_ij * z_ij / r_ij_2
173
+ hess_yy = g_mm * y_ij ** 2 / r_ij_2
174
+ hess_yz = g_mm * y_ij * z_ij / r_ij_2
175
+ hess_zz = g_mm * z_ij ** 2 / r_ij_2
176
+
177
+ # Fill the Hessian matrix
178
+ self.cart_hess[i * 3][i * 3] += hess_xx
179
+ self.cart_hess[i * 3 + 1][i * 3] += hess_xy
180
+ self.cart_hess[i * 3 + 1][i * 3 + 1] += hess_yy
181
+ self.cart_hess[i * 3 + 2][i * 3] += hess_xz
182
+ self.cart_hess[i * 3 + 2][i * 3 + 1] += hess_yz
183
+ self.cart_hess[i * 3 + 2][i * 3 + 2] += hess_zz
184
+
185
+ self.cart_hess[j * 3][j * 3] += hess_xx
186
+ self.cart_hess[j * 3 + 1][j * 3] += hess_xy
187
+ self.cart_hess[j * 3 + 1][j * 3 + 1] += hess_yy
188
+ self.cart_hess[j * 3 + 2][j * 3] += hess_xz
189
+ self.cart_hess[j * 3 + 2][j * 3 + 1] += hess_yz
190
+ self.cart_hess[j * 3 + 2][j * 3 + 2] += hess_zz
191
+
192
+ self.cart_hess[i * 3][j * 3] -= hess_xx
193
+ self.cart_hess[i * 3][j * 3 + 1] -= hess_xy
194
+ self.cart_hess[i * 3][j * 3 + 2] -= hess_xz
195
+ self.cart_hess[i * 3 + 1][j * 3] -= hess_xy
196
+ self.cart_hess[i * 3 + 1][j * 3 + 1] -= hess_yy
197
+ self.cart_hess[i * 3 + 1][j * 3 + 2] -= hess_yz
198
+ self.cart_hess[i * 3 + 2][j * 3] -= hess_xz
199
+ self.cart_hess[i * 3 + 2][j * 3 + 1] -= hess_yz
200
+ self.cart_hess[i * 3 + 2][j * 3 + 2] -= hess_zz
201
+
202
+ self.cart_hess[j * 3][i * 3] -= hess_xx
203
+ self.cart_hess[j * 3][i * 3 + 1] -= hess_xy
204
+ self.cart_hess[j * 3][i * 3 + 2] -= hess_xz
205
+ self.cart_hess[j * 3 + 1][i * 3] -= hess_xy
206
+ self.cart_hess[j * 3 + 1][i * 3 + 1] -= hess_yy
207
+ self.cart_hess[j * 3 + 1][i * 3 + 2] -= hess_yz
208
+ self.cart_hess[j * 3 + 2][i * 3] -= hess_xz
209
+ self.cart_hess[j * 3 + 2][i * 3 + 1] -= hess_yz
210
+ self.cart_hess[j * 3 + 2][i * 3 + 2] -= hess_zz
211
+
212
+ return
213
+
214
+ def swart_angle(self, coord, element_list):
215
+ """Calculate angle bending contributions to the Hessian with D3 dispersion"""
216
+ # Detect cyano groups
217
+ cyano_atoms = self.detect_cyano_groups(coord, element_list)
218
+ cyano_set = set()
219
+ for c_idx, n_idx in cyano_atoms:
220
+ cyano_set.add(c_idx)
221
+ cyano_set.add(n_idx)
222
+
223
+ for i in range(len(coord)):
224
+ for j in range(len(coord)):
225
+ if i == j:
226
+ continue
227
+ x_ij = coord[i][0] - coord[j][0]
228
+ y_ij = coord[i][1] - coord[j][1]
229
+ z_ij = coord[i][2] - coord[j][2]
230
+ r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
231
+ r_ij = np.sqrt(r_ij_2)
232
+ covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
233
+
234
+ # Get D3 parameters for i-j pair
235
+ c6_ij, c8_ij, r0_ij = self.get_d3_parameters(element_list[i], element_list[j])
236
+
237
+ for k in range(j):
238
+ if i == k:
239
+ continue
240
+ x_ik = coord[i][0] - coord[k][0]
241
+ y_ik = coord[i][1] - coord[k][1]
242
+ z_ik = coord[i][2] - coord[k][2]
243
+ r_ik_2 = x_ik**2 + y_ik**2 + z_ik**2
244
+ r_ik = np.sqrt(r_ik_2)
245
+ covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
246
+
247
+ # Check for linear arrangement (cos_theta ~ 1.0)
248
+ error_check = x_ij * x_ik + y_ij * y_ik + z_ij * z_ik
249
+ error_check = error_check / (r_ij * r_ik)
250
+
251
+ if abs(error_check - 1.0) < self.eps:
252
+ continue
253
+
254
+ x_jk = coord[j][0] - coord[k][0]
255
+ y_jk = coord[j][1] - coord[k][1]
256
+ z_jk = coord[j][2] - coord[k][2]
257
+ r_jk_2 = x_jk**2 + y_jk**2 + z_jk**2
258
+ r_jk = np.sqrt(r_jk_2)
259
+
260
+ # Get D3 parameters for i-k pair
261
+ c6_ik, c8_ik, r0_ik = self.get_d3_parameters(element_list[i], element_list[k])
262
+
263
+ # Calculate D3 dispersion contributions
264
+ d3_ij = self.calc_d3_force_const(r_ij, c6_ij, c8_ij, r0_ij)
265
+ d3_ik = self.calc_d3_force_const(r_ik, c6_ik, c8_ik, r0_ik)
266
+
267
+ # Calculate bond force constants with D3 dispersion
268
+ g_ij = self.calc_force_const(1.0, covalent_length_ij, r_ij) + 0.5 * self.kd * d3_ij
269
+ g_ik = self.calc_force_const(1.0, covalent_length_ik, r_ik) + 0.5 * self.kd * d3_ik
270
+
271
+ # Check if angle involves cyano group
272
+ is_cyano_angle = (i in cyano_set or j in cyano_set or k in cyano_set)
273
+
274
+ # Apply appropriate force constant
275
+ if is_cyano_angle:
276
+ # Special force constant for angles involving cyano groups
277
+ g_jk = self.cn_kf * g_ij * g_ik
278
+ else:
279
+ # Regular Swart force constant
280
+ g_jk = self.kf * g_ij * g_ik
281
+
282
+ # Calculate cross product for sin(theta)
283
+ 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
284
+
285
+ if r_cross_2 < 1.0e-12:
286
+ r_cross = 0.0
287
+ else:
288
+ r_cross = np.sqrt(r_cross_2)
289
+
290
+ if r_ik > self.eps and r_ij > self.eps and r_jk > self.eps:
291
+ cos_theta = (r_ij_2 + r_ik_2 - r_jk_2) / (2.0 * r_ij * r_ik)
292
+ sin_theta = r_cross / (r_ij * r_ik)
293
+
294
+ dot_product_r_ij_r_ik = x_ij * x_ik + y_ij * y_ik + z_ij * z_ik
295
+
296
+ if sin_theta > self.eps: # non-linear
297
+ # Calculate derivatives for non-linear case
298
+ s_xj = (x_ij / r_ij * cos_theta - x_ik / r_ik) / (r_ij * sin_theta)
299
+ s_yj = (y_ij / r_ij * cos_theta - y_ik / r_ik) / (r_ij * sin_theta)
300
+ s_zj = (z_ij / r_ij * cos_theta - z_ik / r_ik) / (r_ij * sin_theta)
301
+
302
+ s_xk = (x_ik / r_ik * cos_theta - x_ij / r_ij) / (r_ik * sin_theta)
303
+ s_yk = (y_ik / r_ik * cos_theta - y_ij / r_ij) / (r_ik * sin_theta)
304
+ s_zk = (z_ik / r_ik * cos_theta - z_ij / r_ij) / (r_ik * sin_theta)
305
+
306
+ s_xi = -1 * s_xj - s_xk
307
+ s_yi = -1 * s_yj - s_yk
308
+ s_zi = -1 * s_zj - s_zk
309
+
310
+ s_j = [s_xj, s_yj, s_zj]
311
+ s_k = [s_xk, s_yk, s_zk]
312
+ s_i = [s_xi, s_yi, s_zi]
313
+
314
+ # Update Hessian for non-linear case
315
+ for l in range(3):
316
+ for m in range(3):
317
+ #-------------------------------------
318
+ if i > j:
319
+ tmp_val = g_jk * s_i[l] * s_j[m]
320
+ self.cart_hess[i * 3 + l][j * 3 + m] += tmp_val
321
+ else:
322
+ tmp_val = g_jk * s_j[l] * s_i[m]
323
+ self.cart_hess[j * 3 + l][i * 3 + m] += tmp_val
324
+
325
+ #-------------------------------------
326
+ if i > k:
327
+ tmp_val = g_jk * s_i[l] * s_k[m]
328
+ self.cart_hess[i * 3 + l][k * 3 + m] += tmp_val
329
+ else:
330
+ tmp_val = g_jk * s_k[l] * s_i[m]
331
+ self.cart_hess[k * 3 + l][i * 3 + m] += tmp_val
332
+
333
+ #-------------------------------------
334
+ if j > k:
335
+ tmp_val = g_jk * s_j[l] * s_k[m]
336
+ self.cart_hess[j * 3 + l][k * 3 + m] += tmp_val
337
+ else:
338
+ tmp_val = g_jk * s_k[l] * s_j[m]
339
+ self.cart_hess[k * 3 + l][j * 3 + m] += tmp_val
340
+ #-------------------------------------
341
+
342
+ # Update diagonal blocks
343
+ for l in range(3):
344
+ for m in range(l):
345
+ tmp_val_1 = g_jk * s_j[l] * s_j[m]
346
+ tmp_val_2 = g_jk * s_i[l] * s_i[m]
347
+ tmp_val_3 = g_jk * s_k[l] * s_k[m]
348
+
349
+ self.cart_hess[j * 3 + l][j * 3 + m] += tmp_val_1
350
+ self.cart_hess[i * 3 + l][i * 3 + m] += tmp_val_2
351
+ self.cart_hess[k * 3 + l][k * 3 + m] += tmp_val_3
352
+
353
+ else: # linear
354
+ # Special handling for linear arrangements
355
+ if abs(y_ij) < self.eps and abs(z_ij) < self.eps:
356
+ x_1 = -1 * y_ij
357
+ y_1 = x_ij
358
+ z_1 = 0.0
359
+ x_2 = -1 * x_ij * z_ij
360
+ y_2 = -1 * y_ij * z_ij
361
+ z_2 = x_ij ** 2 + y_ij ** 2
362
+ else:
363
+ x_1 = 1.0
364
+ y_1 = 0.0
365
+ z_1 = 0.0
366
+ x_2 = 0.0
367
+ y_2 = 1.0
368
+ z_2 = 0.0
369
+
370
+ x = [x_1, x_2]
371
+ y = [y_1, y_2]
372
+ z = [z_1, z_2]
373
+
374
+ # Iterate over two perpendicular directions
375
+ for ii in range(2):
376
+ r_1 = np.sqrt(x[ii] ** 2 + y[ii] ** 2 + z[ii] ** 2)
377
+ cos_theta_x = x[ii] / r_1
378
+ cos_theta_y = y[ii] / r_1
379
+ cos_theta_z = z[ii] / r_1
380
+
381
+ s_xj = -1 * cos_theta_x / r_ij
382
+ s_yj = -1 * cos_theta_y / r_ij
383
+ s_zj = -1 * cos_theta_z / r_ij
384
+ s_xk = -1 * cos_theta_x / r_ik
385
+ s_yk = -1 * cos_theta_y / r_ik
386
+ s_zk = -1 * cos_theta_z / r_ik
387
+
388
+ s_xi = -1 * s_xj - s_xk
389
+ s_yi = -1 * s_yj - s_yk
390
+ s_zi = -1 * s_zj - s_zk
391
+
392
+ s_j = [s_xj, s_yj, s_zj]
393
+ s_k = [s_xk, s_yk, s_zk]
394
+ s_i = [s_xi, s_yi, s_zi]
395
+
396
+ # Update Hessian for linear case
397
+ for l in range(3):
398
+ for m in range(3):
399
+ #-------------------------------------
400
+ if i > j:
401
+ tmp_val = g_jk * s_i[l] * s_j[m]
402
+ self.cart_hess[i * 3 + l][j * 3 + m] += tmp_val
403
+ else:
404
+ tmp_val = g_jk * s_j[l] * s_i[m]
405
+ self.cart_hess[j * 3 + l][i * 3 + m] += tmp_val
406
+ #-------------------------------------
407
+ if i > k:
408
+ tmp_val = g_jk * s_i[l] * s_k[m]
409
+ self.cart_hess[i * 3 + l][k * 3 + m] += tmp_val
410
+ else:
411
+ tmp_val = g_jk * s_k[l] * s_i[m]
412
+ self.cart_hess[k * 3 + l][i * 3 + m] += tmp_val
413
+ #-------------------------------------
414
+ if j > k:
415
+ tmp_val = g_jk * s_j[l] * s_k[m]
416
+ self.cart_hess[j * 3 + l][k * 3 + m] += tmp_val
417
+ else:
418
+ tmp_val = g_jk * s_k[l] * s_j[m]
419
+ self.cart_hess[k * 3 + l][j * 3 + m] += tmp_val
420
+ #-------------------------------------
421
+
422
+ # Update diagonal blocks for linear case
423
+ for l in range(3):
424
+ for m in range(l):
425
+ tmp_val_1 = g_jk * s_j[l] * s_j[m]
426
+ tmp_val_2 = g_jk * s_i[l] * s_i[m]
427
+ tmp_val_3 = g_jk * s_k[l] * s_k[m]
428
+
429
+ self.cart_hess[j * 3 + l][j * 3 + m] += tmp_val_1
430
+ self.cart_hess[i * 3 + l][i * 3 + m] += tmp_val_2
431
+ self.cart_hess[k * 3 + l][k * 3 + m] += tmp_val_3
432
+ else:
433
+ pass # Skip if any distance is too small
434
+
435
+ # Make the Hessian symmetric for angle terms
436
+ n_basis = len(coord) * 3
437
+ for i in range(n_basis):
438
+ for j in range(i):
439
+ if abs(self.cart_hess[i][j] - self.cart_hess[j][i]) > 1.0e-10:
440
+ avg = (self.cart_hess[i][j] + self.cart_hess[j][i]) / 2.0
441
+ self.cart_hess[i][j] = avg
442
+ self.cart_hess[j][i] = avg
443
+
444
+ return
445
+
446
+ def swart_dihedral_angle(self, coord, element_list):
447
+ """Calculate dihedral angle contributions to the Hessian with D3 dispersion"""
448
+ # Detect cyano groups
449
+ cyano_atoms = self.detect_cyano_groups(coord, element_list)
450
+ cyano_set = set()
451
+ for c_idx, n_idx in cyano_atoms:
452
+ cyano_set.add(c_idx)
453
+ cyano_set.add(n_idx)
454
+
455
+ for j in range(len(coord)):
456
+ t_xyz_2 = coord[j]
457
+
458
+ for k in range(len(coord)):
459
+ if j >= k:
460
+ continue
461
+ t_xyz_3 = coord[k]
462
+ for i in range(len(coord)):
463
+ ij = (len(coord) * j) + (i + 1)
464
+ if i >= j:
465
+ continue
466
+ if i >= k:
467
+ continue
468
+
469
+ t_xyz_1 = coord[i]
470
+
471
+ for l in range(len(coord)):
472
+ kl = (len(coord) * k) + (l + 1)
473
+
474
+ if ij <= kl:
475
+ continue
476
+ if l >= i:
477
+ continue
478
+ if l >= j:
479
+ continue
480
+ if l >= k:
481
+ continue
482
+
483
+ t_xyz_4 = coord[l]
484
+ r_ij = coord[i] - coord[j]
485
+ r_jk = coord[j] - coord[k]
486
+ r_kl = coord[k] - coord[l]
487
+
488
+ covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
489
+ covalent_length_jk = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
490
+ covalent_length_kl = covalent_radii_lib(element_list[k]) + covalent_radii_lib(element_list[l])
491
+
492
+ # Calculate vector magnitudes
493
+ r_ij_2 = np.sum(r_ij ** 2)
494
+ r_jk_2 = np.sum(r_jk ** 2)
495
+ r_kl_2 = np.sum(r_kl ** 2)
496
+ norm_r_ij = np.sqrt(r_ij_2)
497
+ norm_r_jk = np.sqrt(r_jk_2)
498
+ norm_r_kl = np.sqrt(r_kl_2)
499
+
500
+ # Skip if angle is too shallow (less than 35 degrees)
501
+ a35 = (35.0/180)* np.pi
502
+ cosfi_max = np.cos(a35)
503
+ cosfi2 = np.dot(r_ij, r_jk) / np.sqrt(r_ij_2 * r_jk_2)
504
+ if abs(cosfi2) > cosfi_max:
505
+ continue
506
+ cosfi3 = np.dot(r_kl, r_jk) / np.sqrt(r_kl_2 * r_jk_2)
507
+ if abs(cosfi3) > cosfi_max:
508
+ continue
509
+
510
+ # Get D3 parameters for each atom pair
511
+ c6_ij, c8_ij, r0_ij = self.get_d3_parameters(element_list[i], element_list[j])
512
+ c6_jk, c8_jk, r0_jk = self.get_d3_parameters(element_list[j], element_list[k])
513
+ c6_kl, c8_kl, r0_kl = self.get_d3_parameters(element_list[k], element_list[l])
514
+
515
+ # Calculate D3 dispersion contributions
516
+ d3_ij = self.calc_d3_force_const(norm_r_ij, c6_ij, c8_ij, r0_ij)
517
+ d3_jk = self.calc_d3_force_const(norm_r_jk, c6_jk, c8_jk, r0_jk)
518
+ d3_kl = self.calc_d3_force_const(norm_r_kl, c6_kl, c8_kl, r0_kl)
519
+
520
+ # Calculate bond force constants with D3 dispersion
521
+ g_ij = self.calc_force_const(1.0, covalent_length_ij, norm_r_ij) + 0.5 * self.kd * d3_ij
522
+ g_jk = self.calc_force_const(1.0, covalent_length_jk, norm_r_jk) + 0.5 * self.kd * d3_jk
523
+ g_kl = self.calc_force_const(1.0, covalent_length_kl, norm_r_kl) + 0.5 * self.kd * d3_kl
524
+
525
+ # Check if torsion involves cyano group
526
+ is_cyano_torsion = False
527
+ if i in cyano_set or j in cyano_set or k in cyano_set or l in cyano_set:
528
+ is_cyano_torsion = True
529
+
530
+ # Adjust force constant for cyano groups - they have flatter torsional potentials
531
+ if is_cyano_torsion:
532
+ t_ij = self.cn_kt * g_ij * g_jk * g_kl
533
+ else:
534
+ t_ij = self.kt * g_ij * g_jk * g_kl
535
+
536
+ # Calculate torsion angle and derivatives
537
+ t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
538
+ tau, c = torsion2(t_xyz)
539
+ s_i = c[0]
540
+ s_j = c[1]
541
+ s_k = c[2]
542
+ s_l = c[3]
543
+
544
+ # Update Hessian with torsional contributions
545
+ for n in range(3):
546
+ for m in range(3):
547
+ self.cart_hess[3 * i + n][3 * j + m] += t_ij * s_i[n] * s_j[m]
548
+ self.cart_hess[3 * i + n][3 * k + m] += t_ij * s_i[n] * s_k[m]
549
+ self.cart_hess[3 * i + n][3 * l + m] += t_ij * s_i[n] * s_l[m]
550
+ self.cart_hess[3 * j + n][3 * k + m] += t_ij * s_j[n] * s_k[m]
551
+ self.cart_hess[3 * j + n][3 * l + m] += t_ij * s_j[n] * s_l[m]
552
+ self.cart_hess[3 * k + n][3 * l + m] += t_ij * s_k[n] * s_l[m]
553
+
554
+ # Update diagonal blocks
555
+ for n in range(3):
556
+ for m in range(n):
557
+ self.cart_hess[3 * i + n][3 * i + m] += t_ij * s_i[n] * s_i[m]
558
+ self.cart_hess[3 * j + n][3 * j + m] += t_ij * s_j[n] * s_j[m]
559
+ self.cart_hess[3 * k + n][3 * k + m] += t_ij * s_k[n] * s_k[m]
560
+ self.cart_hess[3 * l + n][3 * l + m] += t_ij * s_l[n] * s_l[m]
561
+
562
+ return
563
+
564
+ def swart_out_of_plane(self, coord, element_list):
565
+ """Calculate out-of-plane bending contributions to the Hessian"""
566
+ # Detect cyano groups
567
+ cyano_atoms = self.detect_cyano_groups(coord, element_list)
568
+ cyano_set = set()
569
+ for c_idx, n_idx in cyano_atoms:
570
+ cyano_set.add(c_idx)
571
+ cyano_set.add(n_idx)
572
+
573
+ for i in range(len(coord)):
574
+ t_xyz_4 = coord[i]
575
+ for j in range(len(coord)):
576
+ if i >= j:
577
+ continue
578
+ t_xyz_1 = coord[j]
579
+ for k in range(len(coord)):
580
+ ij = (len(coord) * j) + (i + 1)
581
+ if i >= k:
582
+ continue
583
+ if j >= k:
584
+ continue
585
+ t_xyz_2 = coord[k]
586
+
587
+ for l in range(len(coord)):
588
+ kl = (len(coord) * k) + (l + 1)
589
+ if i >= l:
590
+ continue
591
+ if j >= l:
592
+ continue
593
+ if k >= l:
594
+ continue
595
+ if ij <= kl:
596
+ continue
597
+ t_xyz_3 = coord[l]
598
+
599
+ r_ij = coord[i] - coord[j]
600
+ covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
601
+
602
+ r_ik = coord[i] - coord[k]
603
+ covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
604
+
605
+ r_il = coord[i] - coord[l]
606
+ covalent_length_il = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[l])
607
+
608
+ r_ij_2 = np.sum(r_ij ** 2)
609
+ r_ik_2 = np.sum(r_ik ** 2)
610
+ r_il_2 = np.sum(r_il ** 2)
611
+ norm_r_ij = np.sqrt(r_ij_2)
612
+ norm_r_ik = np.sqrt(r_ik_2)
613
+ norm_r_il = np.sqrt(r_il_2)
614
+
615
+ # Skip if atoms are nearly collinear
616
+ cosfi2 = np.dot(r_ij, r_ik) / np.sqrt(r_ij_2 * r_ik_2)
617
+ if abs(abs(cosfi2) - 1.0) < 1.0e-1:
618
+ continue
619
+ cosfi3 = np.dot(r_ij, r_il) / np.sqrt(r_ij_2 * r_il_2)
620
+ if abs(abs(cosfi3) - 1.0) < 1.0e-1:
621
+ continue
622
+ cosfi4 = np.dot(r_ik, r_il) / np.sqrt(r_ik_2 * r_il_2)
623
+ if abs(abs(cosfi4) - 1.0) < 1.0e-1:
624
+ continue
625
+
626
+ # Get D3 parameters for each atom pair
627
+ c6_ij, c8_ij, r0_ij = self.get_d3_parameters(element_list[i], element_list[j])
628
+ c6_ik, c8_ik, r0_ik = self.get_d3_parameters(element_list[i], element_list[k])
629
+ c6_il, c8_il, r0_il = self.get_d3_parameters(element_list[i], element_list[l])
630
+
631
+ # Calculate D3 dispersion contributions
632
+ d3_ij = self.calc_d3_force_const(norm_r_ij, c6_ij, c8_ij, r0_ij)
633
+ d3_ik = self.calc_d3_force_const(norm_r_ik, c6_ik, c8_ik, r0_ik)
634
+ d3_il = self.calc_d3_force_const(norm_r_il, c6_il, c8_il, r0_il)
635
+
636
+ # Calculate bond force constants with D3 dispersion
637
+ g_ij = self.calc_force_const(1.0, covalent_length_ij, norm_r_ij) + 0.5 * self.kd * d3_ij
638
+ g_ik = self.calc_force_const(1.0, covalent_length_ik, norm_r_ik) + 0.5 * self.kd * d3_ik
639
+ g_il = self.calc_force_const(1.0, covalent_length_il, norm_r_il) + 0.5 * self.kd * d3_il
640
+
641
+ # Check if any atom is part of a cyano group
642
+ is_cyano_involved = (i in cyano_set or j in cyano_set or
643
+ k in cyano_set or l in cyano_set)
644
+
645
+ # Adjust force constant if cyano group is involved
646
+ if is_cyano_involved:
647
+ t_ij = 0.5 * self.kt * g_ij * g_ik * g_il # Reduce force constant for cyano
648
+ else:
649
+ t_ij = self.kt * g_ij * g_ik * g_il
650
+
651
+ # Calculate out-of-plane angle and derivatives
652
+ t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
653
+ theta, c = outofplane2(t_xyz)
654
+
655
+ s_i = c[0]
656
+ s_j = c[1]
657
+ s_k = c[2]
658
+ s_l = c[3]
659
+
660
+ # Update Hessian with out-of-plane contributions
661
+ for n in range(3):
662
+ for m in range(3):
663
+ self.cart_hess[i * 3 + n][j * 3 + m] += t_ij * s_i[n] * s_j[m]
664
+ self.cart_hess[i * 3 + n][k * 3 + m] += t_ij * s_i[n] * s_k[m]
665
+ self.cart_hess[i * 3 + n][l * 3 + m] += t_ij * s_i[n] * s_l[m]
666
+ self.cart_hess[j * 3 + n][k * 3 + m] += t_ij * s_j[n] * s_k[m]
667
+ self.cart_hess[j * 3 + n][l * 3 + m] += t_ij * s_j[n] * s_l[m]
668
+ self.cart_hess[k * 3 + n][l * 3 + m] += t_ij * s_k[n] * s_l[m]
669
+
670
+ # Update diagonal blocks
671
+ for n in range(3):
672
+ for m in range(n):
673
+ self.cart_hess[i * 3 + n][i * 3 + m] += t_ij * s_i[n] * s_i[m]
674
+ self.cart_hess[j * 3 + n][j * 3 + m] += t_ij * s_j[n] * s_j[m]
675
+ self.cart_hess[k * 3 + n][k * 3 + m] += t_ij * s_k[n] * s_k[m]
676
+ self.cart_hess[l * 3 + n][l * 3 + m] += t_ij * s_l[n] * s_l[m]
677
+
678
+ return
679
+
680
+ def main(self, coord, element_list, cart_gradient):
681
+ """Main method to calculate the approximate Hessian using Swart's model with D3 dispersion"""
682
+ print("Generating Swart's approximate hessian with D3 dispersion correction...")
683
+ self.cart_hess = np.zeros((len(coord)*3, len(coord)*3), dtype="float64")
684
+
685
+ self.swart_bond(coord, element_list)
686
+ self.swart_angle(coord, element_list)
687
+ self.swart_dihedral_angle(coord, element_list)
688
+ self.swart_out_of_plane(coord, element_list)
689
+
690
+ # Ensure symmetry of the Hessian matrix
691
+ for i in range(len(coord)*3):
692
+ for j in range(i):
693
+ if abs(self.cart_hess[i][j]) < 1.0e-10:
694
+ self.cart_hess[i][j] = self.cart_hess[j][i]
695
+ elif abs(self.cart_hess[j][i]) < 1.0e-10:
696
+ self.cart_hess[j][i] = self.cart_hess[i][j]
697
+ else:
698
+ # Average if both elements are non-zero but different
699
+ avg = (self.cart_hess[i][j] + self.cart_hess[j][i]) / 2.0
700
+ self.cart_hess[i][j] = avg
701
+ self.cart_hess[j][i] = avg
702
+
703
+ # Project out translational and rotational modes
704
+ hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
705
+ return hess_proj
706
+