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
multioptpy/IRC/hpc.py ADDED
@@ -0,0 +1,564 @@
1
+ import numpy as np
2
+ import os
3
+ import copy
4
+ import csv
5
+
6
+ from multioptpy.Utils.calc_tools import Calculationtools
7
+ from multioptpy.Optimizer.hessian_update import ModelHessianUpdate
8
+ from multioptpy.Potential.potential import BiasPotentialCalculation
9
+ from multioptpy.Parameters.parameter import UnitValueLib, atomic_mass
10
+ from multioptpy.IRC.converge_criteria import convergence_check
11
+ from multioptpy.Visualization.visualization import Graph
12
+ from multioptpy.PESAnalyzer.calc_irc_curvature import calc_irc_curvature_properties, save_curvature_properties_to_file
13
+
14
+ # --- Helper Class for HPC (DWI) ---
15
+
16
+ class DWISurface:
17
+ """
18
+ Distance Weighted Interpolant (DWI) surface using data from two points
19
+ (position, energy, gradient, Hessian) [cite_start][cite: 79-80].
20
+ This surface can calculate the energy and gradient at any arbitrary point x.
21
+
22
+ Ref: J. Chem. Phys. 120, 9918 (2004), Sec II. [cite_start]D [cite: 239-251]
23
+ """
24
+ def __init__(self, x1, e1, g1, h1, x2, e2, g2, h2):
25
+ self.natoms = x1.shape[0]
26
+ self.dim = self.natoms * 3
27
+
28
+ # Store data as flat (3N,) arrays
29
+ self.x = [x1.flatten(), x2.flatten()]
30
+ self.e = [e1, e2]
31
+ self.g = [g1.flatten(), g2.flatten()]
32
+ self.h = [h1, h2] # (3N, 3N)
33
+
34
+ def get_taylor(self, i, x_flat):
35
+ """Calculates the Taylor expansion T_i(x) using data from point i"""
36
+ dx = x_flat - self.x[i]
37
+ e_taylor = self.e[i] \
38
+ + np.dot(self.g[i].T, dx) \
39
+ + 0.5 * np.dot(dx.T, np.dot(self.h[i], dx))
40
+ return e_taylor
41
+
42
+ def get_taylor_grad(self, i, x_flat):
43
+ """Calculates the gradient ∇T_i(x) of the Taylor expansion T_i(x)"""
44
+ dx = x_flat - self.x[i]
45
+ # ∇T_i = g_i + H_i * (x - x_i)
46
+ g_taylor = self.g[i] + np.dot(self.h[i], dx)
47
+ return g_taylor
48
+
49
+ def get_weights(self, x_flat):
50
+ """Calculate weights w1, w2 at point x [cite: 247-248]"""
51
+ dx1 = x_flat - self.x[0]
52
+ dx2 = x_flat - self.x[1]
53
+
54
+ norm_sq_1 = np.dot(dx1.T, dx1)
55
+ norm_sq_2 = np.dot(dx2.T, dx2)
56
+
57
+ denom = norm_sq_1 + norm_sq_2
58
+
59
+ if denom < 1e-12: # If points are very close (or identical)
60
+ return 0.5, 0.5
61
+
62
+ w1 = norm_sq_2 / denom
63
+ w2 = norm_sq_1 / denom
64
+ return w1, w2
65
+
66
+ def get_weight_grads(self, x_flat):
67
+ """Calculate gradients of the weights ∇w1, ∇w2 at point x"""
68
+ dx1 = x_flat - self.x[0]
69
+ dx2 = x_flat - self.x[1]
70
+
71
+ n1 = np.dot(dx1.T, dx1) # |Δx1|^2
72
+ n2 = np.dot(dx2.T, dx2) # |Δx2|^2
73
+ d = n1 + n2
74
+
75
+ if d < 1e-12:
76
+ return np.zeros(self.dim), np.zeros(self.dim)
77
+
78
+ # ∇n1 = 2 * Δx1
79
+ # ∇n2 = 2 * Δx2
80
+ # ∇d = 2 * (Δx1 + Δx2)
81
+ grad_n1 = 2 * dx1
82
+ grad_n2 = 2 * dx2
83
+ grad_d = grad_n1 + grad_n2
84
+
85
+ # ∇w1 = ∇(n2 / d) = ( (∇n2) * d - n2 * (∇d) ) / d^2
86
+ grad_w1 = (grad_n2 * d - n2 * grad_d) / (d**2)
87
+
88
+ # ∇w2 = ∇(n1 / d) = ( (∇n1) * d - n1 * (∇d) ) / d^2
89
+ grad_w2 = (grad_n1 * d - n1 * grad_d) / (d**2)
90
+
91
+ return grad_w1, grad_w2
92
+
93
+ def get_energy(self, x_flat):
94
+ """Calculate the energy E_DWI(x) on the DWI surface"""
95
+ w1, w2 = self.get_weights(x_flat)
96
+ t1 = self.get_taylor(0, x_flat)
97
+ t2 = self.get_taylor(1, x_flat)
98
+ return w1 * t1 + w2 * t2
99
+
100
+ def get_gradient(self, x_flat):
101
+ """Calculate the gradient ∇E_DWI(x) on the DWI surface"""
102
+ w1, w2 = self.get_weights(x_flat)
103
+ gw1, gw2 = self.get_weight_grads(x_flat)
104
+
105
+ t1 = self.get_taylor(0, x_flat)
106
+ t2 = self.get_taylor(1, x_flat)
107
+
108
+ gt1 = self.get_taylor_grad(0, x_flat)
109
+ gt2 = self.get_taylor_grad(1, x_flat)
110
+
111
+ # ∇(w1*T1 + w2*T2) = (∇w1)T1 + w1(∇T1) + (∇w2)T2 + w2(∇T2)
112
+ grad = (gw1 * t1) + (w1 * gt1) + (gw2 * t2) + (w2 * gt2)
113
+ return grad.reshape(self.natoms, 3)
114
+
115
+ # --- Corrector Step for HPC ---
116
+
117
+ def corrector_step(dwi_surface, x_start, total_s, n_steps=100):
118
+ """
119
+ Finds the corrected point x_corr by integrating on the DWI surface
120
+ using Euler's method.
121
+ dx/ds = -g / |g|
122
+ """
123
+ h = total_s / n_steps # Arc length per step
124
+ x = x_start
125
+
126
+ for _ in range(n_steps):
127
+ g_flat = dwi_surface.get_gradient(x.flatten())
128
+ g = g_flat.flatten()
129
+ norm_g = np.linalg.norm(g)
130
+
131
+ if norm_g < 1e-9: # Reached a minimum
132
+ break
133
+
134
+ # Euler step
135
+ step_vec = - (g / norm_g) * h
136
+ x = x + step_vec.reshape(dwi_surface.natoms, 3)
137
+
138
+ return x
139
+
140
+ # --- Main HPC Class ---
141
+
142
+ class HPC:
143
+ """Hessian-based Predictor-Corrector (HPC) method for IRC calculations
144
+
145
+ This class implements the HPC algorithm which uses
146
+ the LQA method as the predictor and a
147
+ DWI-based corrector.
148
+
149
+ References
150
+ ----------
151
+ [1] J. Chem. Phys. 93, 5634–5642 (1990) (LQA)
152
+ [2] J. Chem. Phys. 120, 9918–9924 (2004) (HPC)
153
+ """
154
+
155
+ def __init__(self, element_list, electric_charge_and_multiplicity, FC_count, file_directory,
156
+ final_directory, force_data, max_step=1000, step_size=0.1, init_coord=None,
157
+ init_hess=None, calc_engine=None, xtb_method=None, **kwargs):
158
+ """Initialize HPC IRC calculator"""
159
+ self.max_step = max_step
160
+ self.step_size = step_size
161
+ self.N_euler = 20000 # Number of Euler integration steps for LQA predictor
162
+ self.N_corrector = 100 # Number of Euler integration steps for DWI corrector
163
+ self.ModelHessianUpdate = ModelHessianUpdate()
164
+ self.CE = calc_engine
165
+ self.FC_count = FC_count
166
+
167
+ # initial condition
168
+ self.coords = init_coord
169
+ self.init_hess = init_hess # This is non-mass-weighted
170
+ self.mw_hessian = None # This is mass-weighted
171
+ self.xtb_method = xtb_method
172
+
173
+ # convergence criteria
174
+ self.MAX_FORCE_THRESHOLD = 0.0004
175
+ self.RMS_FORCE_THRESHOLD = 0.0001
176
+
177
+ self.element_list = element_list
178
+ self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
179
+ self.directory = file_directory
180
+ self.final_directory = final_directory
181
+ self.force_data = force_data
182
+
183
+ # Previous step's data for HPC (non-mass-weighted, bias-inclusive)
184
+ self.prev_data = {
185
+ 'coords': None,
186
+ 'energy': None,
187
+ 'gradient': None,
188
+ 'hessian': None # (3N, 3N)
189
+ }
190
+
191
+ # Store only necessary data
192
+ self.irc_bias_energy_list = []
193
+ self.irc_energy_list = []
194
+ self.irc_mw_coords = []
195
+ self.irc_mw_gradients = []
196
+ self.path_bending_angle_list = []
197
+
198
+ # Create data files
199
+ self.create_csv_file()
200
+ self.create_xyz_file()
201
+
202
+ def create_csv_file(self):
203
+ """Create CSV file for energy and gradient data"""
204
+ self.csv_filename = os.path.join(self.directory, "irc_energies_gradients.csv")
205
+ with open(self.csv_filename, 'w', newline='') as csvfile:
206
+ writer = csv.writer(csvfile)
207
+ writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)', 'RMS Gradient', 'RMS Bias Gradient'])
208
+
209
+ def create_xyz_file(self):
210
+ """Create XYZ file for structure data"""
211
+ self.xyz_filename = os.path.join(self.directory, "irc_structures.xyz")
212
+ # Create empty file (will be appended to later)
213
+ open(self.xyz_filename, 'w').close()
214
+
215
+ def save_to_csv(self, step, energy, bias_energy, gradient, bias_gradient):
216
+ """Save energy and gradient data to CSV file"""
217
+ rms_grad = np.sqrt((gradient**2).mean())
218
+ rms_bias_grad = np.sqrt((bias_gradient**2).mean())
219
+
220
+ with open(self.csv_filename, 'a', newline='') as csvfile:
221
+ writer = csv.writer(csvfile)
222
+ writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad])
223
+
224
+ def save_xyz_structure(self, step, coords):
225
+ """Save molecular structure to XYZ file"""
226
+ coords_angstrom = coords * UnitValueLib().bohr2angstroms
227
+
228
+ with open(self.xyz_filename, 'a') as f:
229
+ f.write(f"{len(coords)}\n")
230
+ f.write(f"IRC Step {step}\n")
231
+ for i, coord in enumerate(coords_angstrom):
232
+ f.write(f"{self.element_list[i]:<3} {coord[0]:15.10f} {coord[1]:15.10f} {coord[2]:15.10f}\n")
233
+
234
+ def get_mass_array(self):
235
+ """Create arrays of atomic masses for mass-weighting operations"""
236
+ elem_mass_list = np.array([atomic_mass(elem) for elem in self.element_list], dtype="float64")
237
+ sqrt_mass_list = np.sqrt(elem_mass_list)
238
+
239
+ three_elem_mass_list = np.repeat(elem_mass_list, 3)
240
+ three_sqrt_mass_list = np.repeat(sqrt_mass_list, 3)
241
+
242
+ return elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list
243
+
244
+ def mass_weight_hessian(self, hessian, three_sqrt_mass_list):
245
+ """Apply mass-weighting to the hessian matrix"""
246
+ mass_mat = np.diag(1.0 / three_sqrt_mass_list)
247
+ return np.dot(mass_mat, np.dot(hessian, mass_mat))
248
+
249
+ def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
250
+ """Convert coordinates to mass-weighted coordinates"""
251
+ mw_coords = copy.deepcopy(coordinates)
252
+ for i in range(len(coordinates)):
253
+ mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
254
+ return mw_coords
255
+
256
+ def mass_weight_gradient(self, gradient, sqrt_mass_list):
257
+ """Convert gradient to mass-weighted form"""
258
+ mw_gradient = copy.deepcopy(gradient)
259
+ for i in range(len(gradient)):
260
+ mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
261
+ return mw_gradient
262
+
263
+ def unmass_weight_step(self, step, sqrt_mass_list):
264
+ """Convert a step vector from mass-weighted to non-mass-weighted coordinates"""
265
+ unmw_step = copy.deepcopy(step)
266
+ for i in range(len(step)):
267
+ unmw_step[i] = step[i] / sqrt_mass_list[i]
268
+ return unmw_step
269
+
270
+ def check_energy_oscillation(self, energy_list):
271
+ """Check if energy is oscillating (going up and down)"""
272
+ if len(energy_list) < 3:
273
+ return False
274
+ last_diff = energy_list[-1] - energy_list[-2]
275
+ prev_diff = energy_list[-2] - energy_list[-3]
276
+ return (last_diff * prev_diff) < 0
277
+
278
+ def step(self, mw_gradient, geom_num_list, mw_combined_hessian, sqrt_mass_list):
279
+ """
280
+ Calculate a single LQA predictor step.
281
+
282
+ Note: This function receives the *combined* (model + bias)
283
+ mass-weighted Hessian.
284
+ """
285
+
286
+ # BFGS update (Hessian is updated in the run loop *before* calling this)
287
+ # self.mw_hessian is now the updated, combined, mass-weighted Hessian
288
+
289
+ eigenvalues, eigenvectors = np.linalg.eigh(mw_combined_hessian)
290
+
291
+ # Drop small eigenvalues
292
+ small_eigvals = np.abs(eigenvalues) < 1e-8
293
+ eigenvalues = eigenvalues[~small_eigvals]
294
+ eigenvectors = eigenvectors[:,~small_eigvals]
295
+
296
+ flattened_gradient = mw_gradient.flatten()
297
+
298
+ # --- MODIFICATION (Fix for numerical stability) ---
299
+ epsilon = 1e-6 # Prevent divergence when gradient norm is near zero
300
+ norm_g = np.linalg.norm(flattened_gradient)
301
+ dt = 1 / self.N_euler * self.step_size / max(norm_g, epsilon)
302
+ # --- END MODIFICATION ---
303
+
304
+ mw_gradient_proj = np.dot(eigenvectors.T, flattened_gradient)
305
+
306
+ # Integration of the step size
307
+ t = dt
308
+ current_length = 0
309
+ for j in range(self.N_euler):
310
+ dsdt = np.sqrt(np.sum(mw_gradient_proj**2 * np.exp(-2*eigenvalues*t)))
311
+ current_length += dsdt * dt
312
+ if current_length > self.step_size:
313
+ break
314
+ t += dt
315
+
316
+ # --- MODIFICATION (Fix for numerical stability) ---
317
+ x = -eigenvalues * t
318
+ small_x_mask = np.abs(x) < 1e-8
319
+ alphas = np.where(
320
+ small_x_mask,
321
+ -t,
322
+ np.expm1(x) / eigenvalues # Numerically stable (exp(x)-1)/eig
323
+ )
324
+ # --- END MODIFICATION ---
325
+
326
+ A = np.dot(eigenvectors, np.dot(np.diag(alphas), eigenvectors.T))
327
+ step = np.dot(A, flattened_gradient)
328
+
329
+ step = step.reshape(len(geom_num_list), 3)
330
+ step = self.unmass_weight_step(step, sqrt_mass_list)
331
+
332
+ new_geom = geom_num_list + step
333
+ new_geom -= Calculationtools().calc_center_of_mass(new_geom, self.element_list)
334
+
335
+ return new_geom
336
+
337
+ def run(self):
338
+ """Run the HPC IRC calculation"""
339
+ print("Hessian-based Predictor-Corrector (HPC) method")
340
+ geom_num_list = self.coords # This is x_k_corr from previous step
341
+ CalcBiaspot = BiasPotentialCalculation(self.directory)
342
+
343
+ oscillation_counter = 0
344
+
345
+ # Get mass arrays for mass-weighting
346
+ elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
347
+
348
+ # --- HPC ALGORITHM FLOW ---
349
+ # At start of loop 'iter', we are at point x_{k-1} (corrected)
350
+ # 1. (LQA Predictor) Use data(k-1) to predict x_k_pred
351
+ # 2. (Ab Initio) Calculate E, g, H at x_k_pred
352
+ # 3. (DWI Surface) Build DWI surface using data(k-1) and data(k_pred)
353
+ # 4. (Corrector) Integrate on DWI from x_{k-1} to get x_k_corr
354
+ # 5. (Store) Save x_k_corr data for next iteration
355
+
356
+ # --- Initialization (iter = 0) ---
357
+ # Perform calculation at the starting point (x_0)
358
+ print("# STEP: 0 (Initialization)")
359
+ e, g, geom_num_list, finish_frag = self.CE.single_point(
360
+ self.final_directory, self.element_list, 0,
361
+ self.electric_charge_and_multiplicity, self.xtb_method,
362
+ UnitValueLib().bohr2angstroms * geom_num_list
363
+ )
364
+ if finish_frag:
365
+ print("Initial calculation failed.")
366
+ return
367
+
368
+ _, B_e, B_g, BPA_hessian = CalcBiaspot.main(
369
+ e, g, geom_num_list, self.element_list,
370
+ self.force_data, g, 0, geom_num_list
371
+ )
372
+
373
+ # Get initial model Hessian
374
+ model_hessian = self.init_hess # Assumes init_hess is provided
375
+ model_hessian = Calculationtools().project_out_hess_tr_and_rot(
376
+ model_hessian, self.element_list, geom_num_list
377
+ )
378
+
379
+ # Store initial (k=0) data
380
+ self.prev_data = {
381
+ 'coords': copy.deepcopy(geom_num_list),
382
+ 'energy': B_e,
383
+ 'gradient': copy.deepcopy(B_g),
384
+ 'hessian': model_hessian + BPA_hessian, # non-MW, bias-inclusive
385
+ 'bpa_hessian': BPA_hessian
386
+ }
387
+
388
+ # Save initial data to files
389
+ self.save_xyz_structure(0, geom_num_list)
390
+ self.save_to_csv(0, e, B_e, g, B_g)
391
+
392
+ # Store for BFGS/oscillation
393
+ self.irc_energy_list.append(e)
394
+ self.irc_bias_energy_list.append(B_e)
395
+ self.irc_mw_coords.append(self.mass_weight_coordinates(geom_num_list, sqrt_mass_list))
396
+ self.irc_mw_gradients.append(self.mass_weight_gradient(B_g, sqrt_mass_list))
397
+
398
+ # --- Main Integration Loop (iter = 1 to max_step) ---
399
+ for iter in range(1, self.max_step):
400
+ print(f"# STEP: {iter}")
401
+ exit_file_detect = os.path.exists(self.directory+"end.txt")
402
+ if exit_file_detect:
403
+ break
404
+
405
+ # --- 1. Predictor Step ---
406
+ # Get data from previous step (k-1) (corrected)
407
+ x_km1 = self.prev_data['coords']
408
+ e_km1 = self.prev_data['energy']
409
+ g_km1 = self.prev_data['gradient'] # non-MW, bias-inclusive
410
+ h_km1 = self.prev_data['hessian'] # non-MW, bias-inclusive
411
+
412
+ # Mass-weight and apply BFGS update
413
+ mw_g_km1 = self.mass_weight_gradient(g_km1, sqrt_mass_list)
414
+ mw_h_km1 = self.mass_weight_hessian(h_km1, three_sqrt_mass_list)
415
+ self.mw_hessian = mw_h_km1 # Start with last step's Hessian
416
+
417
+ if len(self.irc_mw_gradients) > 1:
418
+ delta_g = (self.irc_mw_gradients[-1] - self.irc_mw_gradients[-2]).reshape(-1, 1)
419
+ delta_x = (self.irc_mw_coords[-1] - self.irc_mw_coords[-2]).reshape(-1, 1)
420
+ if np.dot(delta_x.T, delta_g)[0, 0] > 1e-10:
421
+ delta_hess = self.ModelHessianUpdate.BFGS_hessian_update(self.mw_hessian, delta_x, delta_g)
422
+ self.mw_hessian += delta_hess
423
+
424
+ # Predict x_k_pred from x_km1 using LQA
425
+ x_k_pred = self.step(mw_g_km1, x_km1, self.mw_hessian, sqrt_mass_list)
426
+
427
+ # --- 2. Ab Initio Calculation at x_k_pred ---
428
+ e, g, x_k_pred_geom, finish_frag = self.CE.single_point(
429
+ self.final_directory, self.element_list, iter,
430
+ self.electric_charge_and_multiplicity, self.xtb_method,
431
+ UnitValueLib().bohr2angstroms * x_k_pred
432
+ )
433
+ if finish_frag: break
434
+
435
+ # Bias calculation at x_k_pred
436
+ _, e_k_pred, g_k_pred, h_bpa_k_pred = CalcBiaspot.main(
437
+ e, g, x_k_pred_geom, self.element_list,
438
+ self.force_data, g, iter, x_k_pred_geom
439
+ )
440
+
441
+ # Model Hessian calculation at x_k_pred
442
+ if iter % self.FC_count == 0:
443
+ model_h_k_pred = self.CE.Model_hess
444
+ else:
445
+ # Re-use the BFGS-updated Hessian (approximate)
446
+ # Convert mass-weighted back to non-mass-weighted
447
+ inv_mass_mat = np.diag(three_sqrt_mass_list)
448
+ combined_h_non_mw = np.dot(inv_mass_mat, np.dot(self.mw_hessian, inv_mass_mat))
449
+ model_h_k_pred = combined_h_non_mw - self.prev_data['bpa_hessian']
450
+
451
+ model_h_k_pred = Calculationtools().project_out_hess_tr_and_rot(
452
+ model_h_k_pred, self.element_list, x_k_pred_geom
453
+ )
454
+ h_k_pred = model_h_k_pred + h_bpa_k_pred # non-MW, bias-inclusive
455
+
456
+ # --- 3. Build DWI Surface ---
457
+ dwi = DWISurface(
458
+ x_km1, e_km1, g_km1, h_km1, # Point k-1
459
+ x_k_pred_geom, e_k_pred, g_k_pred, h_k_pred # Point k (predicted)
460
+ )
461
+
462
+ # --- 4. Corrector Step ---
463
+ # Integrate on the DWI surface starting from x_km1
464
+ x_k_corr = corrector_step(dwi, x_km1, self.step_size, self.N_corrector)
465
+
466
+ # --- 5. Store and Save ---
467
+ geom_num_list = x_k_corr # This is the new starting point
468
+ self.save_xyz_structure(iter, x_k_corr) # Save the corrected structure
469
+
470
+ # Save the *ab initio* values from the *predicted* point to CSV
471
+ self.save_to_csv(iter, e, e_k_pred, g, g_k_pred)
472
+
473
+ # --- 6. Prepare for next step (k+1) ---
474
+ # Get the energy and gradient at the corrected point from the DWI surface
475
+ e_k_corr = dwi.get_energy(x_k_corr.flatten())
476
+ g_k_corr = dwi.get_gradient(x_k_corr.flatten())
477
+
478
+ # [cite_start]Per HPC paper, use the Hessian from the predicted end point [cite: 81]
479
+ h_k_corr = h_k_pred
480
+
481
+ self.prev_data = {
482
+ 'coords': x_k_corr,
483
+ 'energy': e_k_corr,
484
+ 'gradient': g_k_corr,
485
+ 'hessian': h_k_corr,
486
+ 'bpa_hessian': h_bpa_k_pred # Save for next BFGS approx.
487
+ }
488
+
489
+ # --- Update Data History (for BFGS and oscillation) ---
490
+ if len(self.irc_energy_list) >= 3:
491
+ self.irc_energy_list.pop(0)
492
+ self.irc_bias_energy_list.pop(0)
493
+ self.irc_mw_coords.pop(0)
494
+ self.irc_mw_gradients.pop(0)
495
+
496
+ self.irc_energy_list.append(e) # ab initio E
497
+ self.irc_bias_energy_list.append(e_k_corr) # Corrected bias E
498
+ self.irc_mw_coords.append(self.mass_weight_coordinates(x_k_corr, sqrt_mass_list))
499
+ self.irc_mw_gradients.append(self.mass_weight_gradient(g_k_corr, sqrt_mass_list))
500
+
501
+ # --- Checks ---
502
+ if self.check_energy_oscillation(self.irc_bias_energy_list):
503
+ oscillation_counter += 1
504
+ print(f"Energy oscillation detected ({oscillation_counter}/5)")
505
+ if oscillation_counter >= 5:
506
+ print("Terminating IRC: Energy oscillated for 5 consecutive steps")
507
+ break
508
+ else:
509
+ oscillation_counter = 0
510
+
511
+ # Check convergence using the corrected gradient (DWI gradient)
512
+ if convergence_check(g_k_corr, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
513
+ print("Convergence reached. (HPC-IRC)")
514
+ break
515
+
516
+ # --- Common Output ---
517
+
518
+ # Calculate path bending angle
519
+ if iter > 1: # Needs at least 3 points
520
+ bend_angle = Calculationtools().calc_multi_dim_vec_angle(
521
+ self.irc_mw_coords[0]-self.irc_mw_coords[1],
522
+ self.irc_mw_coords[2]-self.irc_mw_coords[1]
523
+ )
524
+ self.path_bending_angle_list.append(np.degrees(bend_angle))
525
+ print(f"Path bending angle: {np.degrees(bend_angle):.4f}")
526
+
527
+ # Print current (corrected) geometry
528
+ print()
529
+ for i in range(len(geom_num_list)):
530
+ x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
531
+ y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
532
+ z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
533
+ print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
534
+
535
+ # Display information
536
+ print()
537
+ print("Energy (ab initio) : ", e)
538
+ print("Bias Energy (pred) : ", e_k_pred)
539
+ print("Bias Energy (corr) : ", e_k_corr)
540
+ print("RMS B. grad (pred) : ", np.sqrt((g_k_pred**2).mean()))
541
+ print("RMS B. grad (corr) : ", np.sqrt((g_k_corr**2).mean()))
542
+ print("-" * 30)
543
+
544
+ # Save final data visualization
545
+ G = Graph(self.directory)
546
+ rms_gradient_list = []
547
+ with open(self.csv_filename, 'r') as csvfile:
548
+ reader = csv.reader(csvfile)
549
+ next(reader) # Skip header
550
+ for row in reader:
551
+ rms_gradient_list.append(float(row[3]))
552
+
553
+ if self.path_bending_angle_list:
554
+ G.single_plot(
555
+ np.array(range(len(self.path_bending_angle_list))),
556
+ np.array(self.path_bending_angle_list),
557
+ self.directory,
558
+ atom_num=0,
559
+ axis_name_1="# STEP",
560
+ axis_name_2="bending angle [degrees]",
561
+ name="IRC_bending"
562
+ )
563
+
564
+ return