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,633 @@
1
+ import numpy as np
2
+
3
+ from multioptpy.Parameters.parameter import UnitValueLib
4
+ from multioptpy.Utils.calc_tools import Calculationtools
5
+ from multioptpy.Parameters.parameter import GFN0Parameters
6
+
7
+
8
+ class GFN0XTBApproxHessian:
9
+ """GFN0-xTB approximate Hessian with special handling for cyano groups"""
10
+ def __init__(self):
11
+ self.bohr2angstroms = UnitValueLib().bohr2angstroms
12
+ self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
13
+ self.params = GFN0Parameters()
14
+ self.bond_factor = 1.3 # Bond detection threshold factor
15
+
16
+ def detect_bond_connectivity(self, coord, element_list):
17
+ """Calculate bond connectivity matrix and related data"""
18
+ n_atoms = len(coord)
19
+ dist_mat = np.zeros((n_atoms, n_atoms))
20
+
21
+ for i in range(n_atoms):
22
+ for j in range(i+1, n_atoms):
23
+ dist = np.linalg.norm(coord[i] - coord[j])
24
+ dist_mat[i, j] = dist_mat[j, i] = dist
25
+
26
+ # Bond connectivity based on covalent radii
27
+ bond_mat = np.zeros((n_atoms, n_atoms), dtype=bool)
28
+
29
+ for i in range(n_atoms):
30
+ for j in range(i+1, n_atoms):
31
+ r_i = self.params.get_radius(element_list[i])
32
+ r_j = self.params.get_radius(element_list[j])
33
+
34
+ # Use covalent radii to determine bonding
35
+ r_cov = r_i + r_j
36
+
37
+ if dist_mat[i, j] < r_cov * self.bond_factor:
38
+ bond_mat[i, j] = bond_mat[j, i] = True
39
+
40
+ return bond_mat, dist_mat
41
+
42
+ def analyze_molecular_structure(self, coord, element_list):
43
+ """
44
+ Analyze molecular structure to identify bond types, cyano groups, and hybridization
45
+
46
+ Parameters:
47
+ coord: atomic coordinates (Bohr)
48
+ element_list: list of element symbols
49
+
50
+ Returns:
51
+ topology: dictionary with molecular structure information
52
+ """
53
+ # Get bond connectivity
54
+ bond_mat, dist_mat = self.detect_bond_connectivity(coord, element_list)
55
+
56
+ # Build list of bonds
57
+ n_atoms = len(coord)
58
+ bonds = []
59
+
60
+ for i in range(n_atoms):
61
+ for j in range(i+1, n_atoms):
62
+ if bond_mat[i, j]:
63
+ bonds.append((i, j))
64
+
65
+ # Count neighbors for each atom
66
+ neighbor_counts = np.sum(bond_mat, axis=1)
67
+
68
+ # Determine hybridization based on neighbor count and element type
69
+ hybridization = {}
70
+
71
+ for i in range(n_atoms):
72
+ elem = element_list[i]
73
+ n_neighbors = neighbor_counts[i]
74
+
75
+ if elem == 'C':
76
+ if n_neighbors == 4:
77
+ hybridization[i] = 'sp3'
78
+ elif n_neighbors == 3:
79
+ hybridization[i] = 'sp2'
80
+ elif n_neighbors == 2:
81
+ # Check angle to decide between sp and sp2
82
+ neighbors = [j for j in range(n_atoms) if bond_mat[i, j]]
83
+ if len(neighbors) == 2:
84
+ v1 = coord[neighbors[0]] - coord[i]
85
+ v2 = coord[neighbors[1]] - coord[i]
86
+ cos_angle = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
87
+ cos_angle = np.clip(cos_angle, -1.0, 1.0)
88
+ angle = np.arccos(cos_angle) * 180 / np.pi
89
+ hybridization[i] = 'sp' if angle > 160 else 'sp2'
90
+ else:
91
+ hybridization[i] = 'sp2' # Default
92
+ else:
93
+ hybridization[i] = 'sp3' # Default
94
+
95
+ elif elem == 'N':
96
+ if n_neighbors == 3:
97
+ hybridization[i] = 'sp2'
98
+ elif n_neighbors == 2:
99
+ hybridization[i] = 'sp2' # Most common
100
+ elif n_neighbors == 1:
101
+ # Check bond length to determine if it's a triple bond (CN)
102
+ neighbors = [j for j in range(n_atoms) if bond_mat[i, j]]
103
+ if len(neighbors) == 1 and element_list[neighbors[0]] == 'C':
104
+ bond_length = dist_mat[i, neighbors[0]]
105
+ ref_cn_triple = self.params.get_bond_length('C', 'N', 'triple') / self.bohr2angstroms
106
+ if abs(bond_length - ref_cn_triple) < 0.15:
107
+ hybridization[i] = 'sp'
108
+ else:
109
+ hybridization[i] = 'sp2'
110
+ else:
111
+ hybridization[i] = 'sp2'
112
+ else:
113
+ hybridization[i] = 'sp3'
114
+
115
+ elif elem == 'O':
116
+ if n_neighbors == 1:
117
+ # Check if it's a carbonyl
118
+ neighbors = [j for j in range(n_atoms) if bond_mat[i, j]]
119
+ if element_list[neighbors[0]] == 'C':
120
+ bond_length = dist_mat[i, neighbors[0]]
121
+ ref_co_double = self.params.get_bond_length('C', 'O', 'double') / self.bohr2angstroms
122
+ if abs(bond_length - ref_co_double) < 0.15:
123
+ hybridization[i] = 'sp2'
124
+ else:
125
+ hybridization[i] = 'sp3'
126
+ else:
127
+ hybridization[i] = 'sp3'
128
+ else:
129
+ hybridization[i] = 'sp3'
130
+
131
+ else:
132
+ # Default hybridization for other elements
133
+ hybridization[i] = 'sp3'
134
+
135
+ # Identify bond types
136
+ bond_types = {}
137
+
138
+ for i, j in bonds:
139
+ # Determine bond type based on elements and hybridization
140
+ hyb_i = hybridization.get(i, 'sp3')
141
+ hyb_j = hybridization.get(j, 'sp3')
142
+
143
+ # Default is single bond
144
+ bond_type = 'single'
145
+
146
+ # Special case: CN triple bond
147
+ if ((element_list[i] == 'C' and element_list[j] == 'N') or
148
+ (element_list[i] == 'N' and element_list[j] == 'C')):
149
+ if ((hyb_i == 'sp' and hyb_j == 'sp') or
150
+ (hyb_i == 'sp' and neighbor_counts[i] == 2 and neighbor_counts[j] == 1) or
151
+ (hyb_j == 'sp' and neighbor_counts[j] == 2 and neighbor_counts[i] == 1)):
152
+ # This looks like a cyano group
153
+ bond_type = 'triple'
154
+
155
+ # Carbon-carbon bonds
156
+ elif element_list[i] == 'C' and element_list[j] == 'C':
157
+ if hyb_i == 'sp' and hyb_j == 'sp':
158
+ bond_type = 'triple'
159
+ elif hyb_i == 'sp2' and hyb_j == 'sp2':
160
+ # Could be double bond or aromatic
161
+ bond_type = 'double' # Simplified
162
+
163
+ # Carbon-oxygen bonds
164
+ elif ((element_list[i] == 'C' and element_list[j] == 'O') or
165
+ (element_list[i] == 'O' and element_list[j] == 'C')):
166
+ if ((hyb_i == 'sp2' and hyb_j == 'sp2') or
167
+ (hyb_i == 'sp2' and neighbor_counts[j] == 1) or
168
+ (hyb_j == 'sp2' and neighbor_counts[i] == 1)):
169
+ # Carbonyl group
170
+ bond_type = 'double'
171
+
172
+ bond_types[(i, j)] = bond_types[(j, i)] = bond_type
173
+
174
+ # Identify cyano groups
175
+ cyano_groups = []
176
+
177
+ for i in range(n_atoms):
178
+ if element_list[i] == 'C' and hybridization.get(i, '') == 'sp':
179
+ n_partners = [j for j in range(n_atoms) if bond_mat[i, j] and element_list[j] == 'N']
180
+ other_partners = [j for j in range(n_atoms) if bond_mat[i, j] and element_list[j] != 'N']
181
+
182
+ if n_partners and len(other_partners) == 1:
183
+ n_idx = n_partners[0]
184
+ if bond_types.get((i, n_idx)) == 'triple':
185
+ cyano_groups.append((i, n_idx))
186
+
187
+ # Build lists of angles and dihedrals
188
+ angles = []
189
+ dihedrals = []
190
+
191
+ # Extract angles
192
+ for j in range(n_atoms):
193
+ bonded_to_j = [i for i in range(n_atoms) if bond_mat[i, j] and i != j]
194
+ for i in bonded_to_j:
195
+ for k in bonded_to_j:
196
+ if i < k: # Avoid duplicates
197
+ angles.append((i, j, k))
198
+
199
+ # Extract dihedrals
200
+ for j, k in bonds:
201
+ bonded_to_j = [i for i in range(n_atoms) if bond_mat[i, j] and i != j and i != k]
202
+ bonded_to_k = [l for l in range(n_atoms) if bond_mat[k, l] and l != k and l != j]
203
+
204
+ for i in bonded_to_j:
205
+ for l in bonded_to_k:
206
+ if i != l: # Avoid improper dihedrals here
207
+ dihedrals.append((i, j, k, l))
208
+
209
+ # Collect structure information
210
+ topology = {
211
+ 'bonds': bonds,
212
+ 'bond_types': bond_types,
213
+ 'angles': angles,
214
+ 'dihedrals': dihedrals,
215
+ 'bond_mat': bond_mat,
216
+ 'dist_mat': dist_mat,
217
+ 'hybridization': hybridization,
218
+ 'cyano_groups': cyano_groups,
219
+ 'neighbor_counts': neighbor_counts
220
+ }
221
+
222
+ return topology
223
+
224
+ def compute_partial_charges(self, coord, element_list, topology):
225
+ """
226
+ Calculate partial charges using GFN0-xTB electronegativity equilibration
227
+
228
+ Parameters:
229
+ coord: atomic coordinates (Bohr)
230
+ element_list: list of element symbols
231
+ topology: molecular structure information
232
+
233
+ Returns:
234
+ charges: array of partial charges
235
+ """
236
+ n_atoms = len(coord)
237
+ bond_mat = topology['bond_mat']
238
+ dist_mat = topology['dist_mat']
239
+
240
+ # Initialize charges
241
+ charges = np.zeros(n_atoms)
242
+
243
+ # Step 1: Initial charge distribution based on electronegativity difference
244
+ for i, j in topology['bonds']:
245
+ en_i = self.params.get_en(element_list[i])
246
+ en_j = self.params.get_en(element_list[j])
247
+
248
+ # EN difference determines charge flow direction
249
+ en_diff = en_j - en_i
250
+
251
+ # Basic charge transfer based on EN difference
252
+ transfer = 0.05 * np.tanh(0.2 * en_diff)
253
+
254
+ # Apply charge transfer
255
+ charges[i] += transfer
256
+ charges[j] -= transfer
257
+
258
+ # Step 2: Special treatment for cyano groups
259
+ for c_idx, n_idx in topology['cyano_groups']:
260
+ # Cyano groups have strong polarization
261
+ charges[n_idx] -= 0.3 # Negative charge on N
262
+ charges[c_idx] += 0.3 # Positive charge on C
263
+
264
+ # Step 3: Normalize charges to ensure neutrality
265
+ charges -= np.mean(charges)
266
+
267
+ return charges
268
+
269
+ def gfn0_bond_hessian(self, coord, element_list, topology):
270
+ """
271
+ Calculate bond stretching contributions to the Hessian
272
+
273
+ Parameters:
274
+ coord: atomic coordinates (Bohr)
275
+ element_list: list of element symbols
276
+ topology: molecular structure information
277
+ """
278
+ bonds = topology['bonds']
279
+ bond_types = topology['bond_types']
280
+
281
+ for i, j in bonds:
282
+ r_vec = coord[j] - coord[i]
283
+ r_ij = np.linalg.norm(r_vec)
284
+
285
+ # Get bond type
286
+ bond_type = bond_types.get((i, j), 'single')
287
+
288
+ # Get force constant
289
+ force_const = self.params.get_bond_force_constant(
290
+ element_list[i], element_list[j], bond_type)
291
+
292
+ # Get reference bond length (convert to Bohr)
293
+ r0 = self.params.get_bond_length(
294
+ element_list[i], element_list[j], bond_type) / self.bohr2angstroms
295
+
296
+ # Calculate unit vector and projection operator
297
+ if r_ij > 1e-10:
298
+ u_ij = r_vec / r_ij
299
+ proj_op = np.outer(u_ij, u_ij)
300
+ else:
301
+ # Avoid division by zero
302
+ proj_op = np.eye(3) / 3.0
303
+
304
+ # Add to Cartesian Hessian
305
+ h_diag = force_const * proj_op
306
+
307
+ for n in range(3):
308
+ for m in range(3):
309
+ self.cart_hess[3*i+n, 3*i+m] += h_diag[n, m]
310
+ self.cart_hess[3*j+n, 3*j+m] += h_diag[n, m]
311
+ self.cart_hess[3*i+n, 3*j+m] -= h_diag[n, m]
312
+ self.cart_hess[3*j+n, 3*i+m] -= h_diag[n, m]
313
+
314
+ def gfn0_angle_hessian(self, coord, element_list, topology):
315
+ """
316
+ Calculate angle bending contributions to the Hessian
317
+
318
+ Parameters:
319
+ coord: atomic coordinates (Bohr)
320
+ element_list: list of element symbols
321
+ topology: molecular structure information
322
+ """
323
+ angles = topology['angles']
324
+ hybridization = topology['hybridization']
325
+ cyano_groups = topology['cyano_groups']
326
+
327
+ # Create lookup for cyano groups
328
+ cyano_carbons = [c for c, _ in cyano_groups]
329
+ cyano_nitrogens = [n for _, n in cyano_groups]
330
+
331
+ for i, j, k in angles:
332
+ # Calculate vectors and distances
333
+ r_ji = coord[i] - coord[j]
334
+ r_jk = coord[k] - coord[j]
335
+ r_ji_len = np.linalg.norm(r_ji)
336
+ r_jk_len = np.linalg.norm(r_jk)
337
+
338
+ # Skip if atoms are too close
339
+ if r_ji_len < 1e-10 or r_jk_len < 1e-10:
340
+ continue
341
+
342
+ # Special handling for X-C≡N angles
343
+ is_cyano_angle = False
344
+
345
+ if j in cyano_carbons:
346
+ n_idx = None
347
+ for c, n in cyano_groups:
348
+ if c == j:
349
+ n_idx = n
350
+ break
351
+
352
+ if n_idx is not None and (i == n_idx or k == n_idx):
353
+ is_cyano_angle = True
354
+ force_const = self.params.CNParams['kBend']
355
+ theta0 = np.pi # 180 degrees (linear)
356
+
357
+ # Regular angle if not cyano
358
+ if not is_cyano_angle:
359
+ # Get hybridization of central atom
360
+ hyb_j = hybridization.get(j, 'sp3')
361
+
362
+ # Get natural angle based on hybridization
363
+ theta0 = self.params.naturalAngles.get(hyb_j, self.params.naturalAngles['sp3'])
364
+
365
+ # Base force constant
366
+ force_const = self.params.kAngleBase
367
+
368
+ # Scale force constant based on central atom
369
+ if element_list[j] == 'C':
370
+ force_const *= 1.0
371
+ elif element_list[j] == 'N':
372
+ force_const *= 0.9
373
+ elif element_list[j] == 'O':
374
+ force_const *= 0.8
375
+ else:
376
+ force_const *= 0.7
377
+
378
+ # Calculate angle
379
+ cos_theta = np.dot(r_ji, r_jk) / (r_ji_len * r_jk_len)
380
+ cos_theta = np.clip(cos_theta, -0.999999, 0.999999) # Avoid numerical issues
381
+ theta = np.arccos(cos_theta)
382
+
383
+ # Calculate derivatives
384
+ sin_theta = np.sin(theta)
385
+ if sin_theta < 1e-10:
386
+ # Handle nearly linear case
387
+ continue
388
+
389
+ # Simplified derivatives for angle
390
+ d_i = (np.cross(np.cross(r_ji, r_jk), r_ji)) / (r_ji_len**2 * r_jk_len * sin_theta)
391
+ d_k = (np.cross(np.cross(r_jk, r_ji), r_jk)) / (r_ji_len * r_jk_len**2 * sin_theta)
392
+ d_j = -d_i - d_k
393
+
394
+ # Scale derivatives by force constant
395
+ d_i *= np.sqrt(force_const)
396
+ d_j *= np.sqrt(force_const)
397
+ d_k *= np.sqrt(force_const)
398
+
399
+ # Add to Hessian
400
+ for a in range(3):
401
+ for b in range(3):
402
+ # i-i block
403
+ self.cart_hess[3*i+a, 3*i+b] += d_i[a] * d_i[b]
404
+
405
+ # j-j block
406
+ self.cart_hess[3*j+a, 3*j+b] += d_j[a] * d_j[b]
407
+
408
+ # k-k block
409
+ self.cart_hess[3*k+a, 3*k+b] += d_k[a] * d_k[b]
410
+
411
+ # Cross terms
412
+ self.cart_hess[3*i+a, 3*j+b] += d_i[a] * d_j[b]
413
+ self.cart_hess[3*i+a, 3*k+b] += d_i[a] * d_k[b]
414
+ self.cart_hess[3*j+a, 3*i+b] += d_j[a] * d_i[b]
415
+ self.cart_hess[3*j+a, 3*k+b] += d_j[a] * d_k[b]
416
+ self.cart_hess[3*k+a, 3*i+b] += d_k[a] * d_i[b]
417
+ self.cart_hess[3*k+a, 3*j+b] += d_k[a] * d_j[b]
418
+
419
+ def gfn0_torsion_hessian(self, coord, element_list, topology):
420
+ """
421
+ Calculate torsion (dihedral) contributions to the Hessian
422
+
423
+ Parameters:
424
+ coord: atomic coordinates (Bohr)
425
+ element_list: list of element symbols
426
+ topology: molecular structure information
427
+ """
428
+ dihedrals = topology['dihedrals']
429
+ bond_types = topology['bond_types']
430
+ cyano_groups = topology['cyano_groups']
431
+ # Create lookup for cyano groups
432
+ cyano_bonds = set()
433
+ for c, n in cyano_groups:
434
+ cyano_bonds.add((c, n))
435
+ cyano_bonds.add((n, c))
436
+
437
+ for i, j, k, l in dihedrals:
438
+ # Calculate vectors along the bonds
439
+ r_ij = coord[j] - coord[i]
440
+ r_jk = coord[k] - coord[j]
441
+ r_kl = coord[l] - coord[k]
442
+
443
+ # Calculate cross products for the dihedral
444
+ n1 = np.cross(r_ij, r_jk)
445
+ n2 = np.cross(r_jk, r_kl)
446
+
447
+ # Skip if any of the cross products are too small
448
+ n1_norm = np.linalg.norm(n1)
449
+ n2_norm = np.linalg.norm(n2)
450
+ r_jk_norm = np.linalg.norm(r_jk)
451
+
452
+ if n1_norm < 1e-10 or n2_norm < 1e-10 or r_jk_norm < 1e-10:
453
+ continue
454
+
455
+ # Calculate the dihedral angle
456
+ cos_phi = np.dot(n1, n2) / (n1_norm * n2_norm)
457
+ cos_phi = np.clip(cos_phi, -0.999999, 0.999999) # Avoid numerical issues
458
+
459
+ sin_phi = np.dot(np.cross(n1, n2), r_jk) / (n1_norm * n2_norm * r_jk_norm)
460
+ phi = np.arctan2(sin_phi, cos_phi)
461
+
462
+ # Get torsion parameters
463
+ # Check if this is a cyano torsion
464
+ if (j, k) in cyano_bonds or (k, j) in cyano_bonds:
465
+ # Cyano group torsion - very small barrier
466
+ V2 = V3 = self.params.CNParams['kTorsion']
467
+ else:
468
+ # Normal torsion
469
+ bond_type = bond_types.get((j, k), 'single')
470
+
471
+ # Adjust based on bond type
472
+ if bond_type == 'triple':
473
+ # Triple bonds have near-zero torsion barriers
474
+ V2 = V3 = 0.001
475
+ elif bond_type == 'double':
476
+ # Double bonds have significant V2 (two-fold) term
477
+ V2 = self.params.V2Base * 2.0
478
+ V3 = self.params.V3Base * 0.5
479
+ elif bond_type == 'aromatic':
480
+ # Aromatic bonds have mixed character
481
+ V2 = self.params.V2Base * 1.5
482
+ V3 = self.params.V3Base
483
+ else:
484
+ # Single bonds have V3 (three-fold) term
485
+ V2 = self.params.V2Base * 0.5
486
+ V3 = self.params.V3Base * 1.5
487
+
488
+ # Calculate second derivatives
489
+ # V = V2/2 * (1-cos(2*phi)) + V3/2 * (1+cos(3*phi))
490
+ # For second derivatives, we need:
491
+ # d²V/dphi² = 2*V2*cos(2*phi) - 4.5*V3*cos(3*phi)
492
+ d2V = 2.0 * V2 * np.cos(2*phi) - 4.5 * V3 * np.cos(3*phi)
493
+
494
+ # Calculate derivatives of phi w.r.t. Cartesian coordinates
495
+ # (simplified approach)
496
+ # Calculate unit vectors
497
+ e_ij = r_ij / np.linalg.norm(r_ij) if np.linalg.norm(r_ij) > 1e-10 else np.zeros(3)
498
+ e_jk = r_jk / r_jk_norm
499
+ e_kl = r_kl / np.linalg.norm(r_kl) if np.linalg.norm(r_kl) > 1e-10 else np.zeros(3)
500
+
501
+ # Simplified derivatives calculation
502
+ n1_u = n1 / n1_norm if n1_norm > 1e-10 else np.zeros(3)
503
+ n2_u = n2 / n2_norm if n2_norm > 1e-10 else np.zeros(3)
504
+
505
+ # Calculate derivatives (this is a simplified approach)
506
+ # We calculate d(phi)/dr for each atom
507
+ g_i = np.cross(e_ij, n1_u) / (np.linalg.norm(r_ij) * sin_phi) if sin_phi > 1e-10 else np.zeros(3)
508
+ g_l = -np.cross(e_kl, n2_u) / (np.linalg.norm(r_kl) * sin_phi) if sin_phi > 1e-10 else np.zeros(3)
509
+
510
+ # Use conservation of angular momentum to get the middle terms
511
+ # These are simplified and not analytically perfect
512
+ g_j = -g_i - (r_jk_norm / np.linalg.norm(r_ij)) * g_i
513
+ g_k = -g_l - (r_jk_norm / np.linalg.norm(r_kl)) * g_l
514
+
515
+ # Scale derivatives by second derivative of potential
516
+ g_i *= np.sqrt(abs(d2V))
517
+ g_j *= np.sqrt(abs(d2V))
518
+ g_k *= np.sqrt(abs(d2V))
519
+ g_l *= np.sqrt(abs(d2V))
520
+
521
+ # Add to Hessian
522
+ atoms = [i, j, k, l]
523
+ derivatives = [g_i, g_j, g_k, g_l]
524
+
525
+ for a, g_a in enumerate(derivatives):
526
+ for b, g_b in enumerate(derivatives):
527
+ atom_a = atoms[a]
528
+ atom_b = atoms[b]
529
+ for x in range(3):
530
+ for y in range(3):
531
+ self.cart_hess[3*atom_a+x, 3*atom_b+y] += g_a[x] * g_b[y]
532
+
533
+ def gfn0_nonbonded_hessian(self, coord, element_list, topology):
534
+ """
535
+ Calculate non-bonded interaction contributions to the Hessian
536
+
537
+ Parameters:
538
+ coord: atomic coordinates (Bohr)
539
+ element_list: list of element symbols
540
+ topology: molecular structure information
541
+ """
542
+ n_atoms = len(coord)
543
+ bond_mat = topology['bond_mat']
544
+ charges = self.compute_partial_charges(coord, element_list, topology)
545
+
546
+ # Simplified non-bonded model for Hessian approximation
547
+ for i in range(n_atoms):
548
+ for j in range(i+1, n_atoms):
549
+ # Skip bonded atoms and 1-3 interactions
550
+ if bond_mat[i, j]:
551
+ continue
552
+
553
+ # Skip 1-3 interactions (atoms sharing a bonded neighbor)
554
+ has_common_neighbor = False
555
+ for k in range(n_atoms):
556
+ if bond_mat[i, k] and bond_mat[j, k]:
557
+ has_common_neighbor = True
558
+ break
559
+
560
+ if has_common_neighbor:
561
+ continue
562
+
563
+ # Calculate distance vector and magnitude
564
+ r_vec = coord[j] - coord[i]
565
+ r_ij = np.linalg.norm(r_vec)
566
+
567
+ if r_ij < 0.5: # Avoid too small distances
568
+ continue
569
+
570
+ # Calculate unit vector and projection operator
571
+ u_ij = r_vec / r_ij
572
+ proj_op = np.outer(u_ij, u_ij)
573
+
574
+ # Get atomic radii
575
+ r_i = self.params.get_radius(element_list[i])
576
+ r_j = self.params.get_radius(element_list[j])
577
+
578
+ # Simple repulsion term (r^-12)
579
+ rep_scale = 0.05 # Scale factor for repulsion
580
+ rep_sum = r_i + r_j
581
+ rep_term = rep_scale * ((rep_sum / r_ij)**12)
582
+
583
+ # Electrostatic term (q_i * q_j / r)
584
+ elec_scale = 0.1 # Scale factor for electrostatics
585
+ elec_term = elec_scale * charges[i] * charges[j] / r_ij
586
+
587
+ # Combine terms for total non-bonded Hessian contribution
588
+ hess_factor = (12.0 * rep_term / r_ij**2) + (2.0 * elec_term / r_ij**2)
589
+
590
+ # Add to Cartesian Hessian
591
+ for n in range(3):
592
+ for m in range(3):
593
+ self.cart_hess[3*i+n, 3*i+m] += hess_factor * proj_op[n, m]
594
+ self.cart_hess[3*j+n, 3*j+m] += hess_factor * proj_op[n, m]
595
+ self.cart_hess[3*i+n, 3*j+m] -= hess_factor * proj_op[n, m]
596
+ self.cart_hess[3*j+n, 3*i+m] -= hess_factor * proj_op[n, m]
597
+
598
+ def main(self, coord, element_list, cart_gradient):
599
+ """
600
+ Calculate Hessian using GFN0-xTB model
601
+
602
+ Parameters:
603
+ coord: Atomic coordinates (N×3 array, Bohr)
604
+ element_list: List of element symbols
605
+ cart_gradient: Gradient in Cartesian coordinates
606
+
607
+ Returns:
608
+ hess_proj: Hessian with rotational and translational modes projected out
609
+ """
610
+ print("Generating Hessian using GFN0-xTB model...")
611
+
612
+ # Initialize Hessian matrix
613
+ n_atoms = len(coord)
614
+ self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
615
+
616
+ # Analyze molecular structure
617
+ topology = self.analyze_molecular_structure(coord, element_list)
618
+
619
+ # Calculate different Hessian components
620
+ self.gfn0_bond_hessian(coord, element_list, topology)
621
+ self.gfn0_angle_hessian(coord, element_list, topology)
622
+ self.gfn0_torsion_hessian(coord, element_list, topology)
623
+ self.gfn0_nonbonded_hessian(coord, element_list, topology)
624
+
625
+ # Symmetrize the Hessian matrix
626
+ for i in range(n_atoms*3):
627
+ for j in range(i):
628
+ self.cart_hess[j, i] = self.cart_hess[i, j]
629
+
630
+ # Project out rotational and translational modes
631
+ hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
632
+
633
+ return hess_proj