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.py ADDED
@@ -0,0 +1,529 @@
1
+ import numpy as np
2
+ import os
3
+ import glob
4
+ import csv
5
+
6
+ from multioptpy.Potential.potential import BiasPotentialCalculation
7
+ from multioptpy.Parameters.parameter import atomic_mass
8
+ from multioptpy.Utils.calc_tools import Calculationtools
9
+ from multioptpy.IRC.lqa import LQA
10
+ from multioptpy.IRC.hpc import HPC
11
+ from multioptpy.IRC.rk4 import RK4
12
+ from multioptpy.IRC.dvv import DVV
13
+ from multioptpy.IRC.modekill import ModeKill
14
+ from multioptpy.IRC.euler import Euler
15
+ from multioptpy.IRC.converge_criteria import convergence_check
16
+ from multioptpy.fileio import traj2list
17
+
18
+ ### I recommend to use LQA method to calculate IRC path ###
19
+
20
+ class IRC:
21
+ """Main class for Intrinsic Reaction Coordinate calculations
22
+
23
+ This class handles saddle point verification, forward/backward IRC calculations
24
+ """
25
+
26
+ def __init__(self, directory, final_directory, irc_method, QM_interface, element_list,
27
+ electric_charge_and_multiplicity, force_data, xtb_method, FC_count=-1, hessian=None):
28
+ """Initialize IRC calculator
29
+
30
+ Parameters
31
+ ----------
32
+ directory : str
33
+ Working directory
34
+ final_directory : str
35
+ Directory for final output
36
+ irc_method : list
37
+ [step_size, max_step, method_name]
38
+ QM_interface : object
39
+ Interface to quantum mechanical calculator
40
+ element_list : list
41
+ List of atomic elements
42
+ electric_charge_and_multiplicity : tuple
43
+ Charge and multiplicity for the system
44
+ force_data : dict
45
+ Force field data for bias potential
46
+ xtb_method : str
47
+ XTB method specification
48
+ FC_count : int, optional
49
+ Frequency of full hessian recalculation, default=-1
50
+ hessian : numpy.ndarray, optional
51
+ Initial hessian matrix, default=None
52
+ """
53
+ if hessian is None:
54
+ self.hessian_flag = False
55
+ else:
56
+ self.hessian_flag = True
57
+ self.hessian = hessian
58
+
59
+ self.step_size = float(irc_method[0])
60
+ self.max_step = int(irc_method[1])
61
+ self.method = str(irc_method[2])
62
+ tmp_method = irc_method[2].split(":")
63
+ if len(tmp_method) > 1:
64
+ self.method = tmp_method[0]
65
+ self.method_options = tmp_method[1:]
66
+ else:
67
+ self.method = irc_method[2]
68
+ self.method_options = []
69
+
70
+ self.file_directory = os.path.abspath(directory)+"/"
71
+ self.final_directory = os.path.abspath(final_directory)+"/"
72
+ self.QM_interface = QM_interface
73
+
74
+ self.element_list = element_list
75
+ self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
76
+ self.xtb_method = xtb_method
77
+
78
+ self.force_data = force_data
79
+ self.FC_count = FC_count
80
+
81
+ # convergence criteria
82
+ self.MAX_FORCE_THRESHOLD = 0.0004
83
+ self.RMS_FORCE_THRESHOLD = 0.0001
84
+
85
+ # Will be set in saddle_check
86
+ self.IRC_flag = False
87
+ self.initial_step = None
88
+ self.geom_num_list = None
89
+ self.ts_coords = None
90
+ self.fin_xyz_base = None # Added
91
+ self.terminal_struct_paths = [] # Added
92
+
93
+ def saddle_check(self):
94
+ """Check if starting point is a saddle point and calculate initial displacement
95
+
96
+ Returns
97
+ -------
98
+ numpy.ndarray
99
+ Initial displacement vector
100
+ bool
101
+ True if valid IRC starting point (has 1 imaginary mode)
102
+ numpy.ndarray
103
+ Current geometry coordinates
104
+ bool
105
+ True if calculation failed
106
+ """
107
+ # Setup hessian calculation
108
+ if not self.hessian_flag:
109
+ self.QM_interface.hessian_flag = True
110
+ iter = 1
111
+ else:
112
+ self.QM_interface.FC_COUNT = -1
113
+ iter = 1
114
+
115
+ # Read input geometry
116
+ fin_xyz = glob.glob(self.final_directory+"/*.xyz")
117
+ # --- Added ---
118
+ if fin_xyz:
119
+ self.fin_xyz_base = os.path.basename(fin_xyz[0]).split('.')[0]
120
+ else:
121
+ print("Warning: No XYZ file found in final_directory. Using default name 'input' for terminal structures.")
122
+ self.fin_xyz_base = "input"
123
+ # --- End of addition ---
124
+
125
+ with open(fin_xyz[0], "r") as f:
126
+ words = f.read().splitlines()
127
+ geom_num_list = []
128
+ for i in range(len(words)):
129
+ if len(words[i].split()) > 3:
130
+ geom_num_list.append([
131
+ float(words[i].split()[1]),
132
+ float(words[i].split()[2]),
133
+ float(words[i].split()[3])
134
+ ])
135
+ geom_num_list = np.array(geom_num_list)
136
+ # Calculate energy, gradient and hessian
137
+ init_e, init_g, geom_num_list, finish_frag = self.QM_interface.single_point(
138
+ self.final_directory,
139
+ self.element_list,
140
+ iter,
141
+ self.electric_charge_and_multiplicity,
142
+ self.xtb_method,
143
+ geom_num_list
144
+ )
145
+
146
+ # Reset QM interface settings
147
+ self.QM_interface.hessian_flag = False
148
+ self.QM_interface.FC_COUNT = self.FC_count
149
+
150
+ if finish_frag:
151
+ return 0, 0, 0, finish_frag
152
+
153
+ # Get hessian from QM calculation
154
+ self.hessian = self.QM_interface.Model_hess
155
+ self.QM_interface.hessian_flag = False
156
+
157
+ # Calculate bias potential
158
+ CalcBiaspot = BiasPotentialCalculation(self.final_directory)
159
+ _, init_B_e, init_B_g, BPA_hessian = CalcBiaspot.main(
160
+ init_e, init_g, geom_num_list, self.element_list,
161
+ self.force_data, init_g, 0, geom_num_list
162
+ )
163
+ isconverged = convergence_check(init_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD)
164
+ # Add bias potential hessian
165
+ self.hessian += BPA_hessian
166
+
167
+ # Project out translational and rotational modes
168
+ self.hessian = Calculationtools().project_out_hess_tr_and_rot(
169
+ self.hessian, self.element_list, geom_num_list
170
+ )
171
+
172
+ # Store initial state
173
+ self.init_e = init_e
174
+ self.init_g = init_g
175
+ self.init_B_e = init_B_e
176
+ self.init_B_g = init_B_g
177
+
178
+ # Find imaginary modes (negative eigenvalues)
179
+ eigenvalues, eigenvectors = np.linalg.eigh(self.hessian)
180
+ neg_indices = np.where(eigenvalues < -1e-8)[0]
181
+ imaginary_count = len(neg_indices)
182
+ print("Number of imaginary eigenvalues: ", imaginary_count)
183
+
184
+ # Determine initial step direction
185
+ if self.method.upper() == "MODEKILL":
186
+ print("Execute ModeKill")
187
+ # ModeKill: Remove imaginary modes using ModeKill class
188
+ IRC_flag = False
189
+ gradient = self.QM_interface.gradient.reshape(len(geom_num_list), 3)
190
+ initial_step = np.zeros_like(gradient)
191
+
192
+
193
+ elif imaginary_count == 1 and isconverged:
194
+ print("Execute IRC")
195
+ # True IRC: Use transition vector (imaginary mode)
196
+ imaginary_idx = neg_indices[0]
197
+ transition_vector = eigenvectors[:, imaginary_idx].reshape(len(geom_num_list), 3)
198
+ initial_step = transition_vector / np.linalg.norm(transition_vector.flatten()) * self.step_size * 0.1
199
+ IRC_flag = True
200
+ else:
201
+ print("Execute meta-IRC")
202
+ # Meta-IRC: Use gradient direction
203
+ gradient = self.QM_interface.gradient.reshape(len(geom_num_list), 3)
204
+ initial_step = gradient / np.linalg.norm(gradient.flatten()) * self.step_size * 0.1
205
+
206
+ # Mass-weight the initial step for meta-IRC
207
+ sqrt_mass_list = np.sqrt([atomic_mass(elem) for elem in self.element_list])
208
+ for i in range(len(initial_step)):
209
+ initial_step[i] /= sqrt_mass_list[i]
210
+
211
+ IRC_flag = False
212
+
213
+ return initial_step, IRC_flag, geom_num_list, finish_frag
214
+
215
+ # --- New method added ---
216
+ def write_xyz(self, filename, geometry):
217
+ """Write a single geometry to an XYZ file"""
218
+ with open(filename, 'w') as outfile:
219
+ outfile.write(f"{len(geometry)}\n")
220
+ outfile.write("Terminal structure\n") # Comment line
221
+ for i in range(len(geometry)):
222
+ outfile.write(f"{self.element_list[i]} {' '.join(map(str, geometry[i]))}\n")
223
+ # --- End of new method ---
224
+
225
+ def _get_irc_method_class(self):
226
+ """Get the appropriate IRC method class based on method name
227
+
228
+ Returns
229
+ -------
230
+ class
231
+ IRC method class (LQA, RK4, ModeKill, Euler, or DVV)
232
+ """
233
+ method_map = {
234
+ "LQA": LQA,
235
+ "RK4": RK4,
236
+ "MODEKILL": ModeKill,
237
+ "EULER": Euler,
238
+ "DVV": DVV,
239
+ "HPC": HPC
240
+ }
241
+
242
+ method_key = self.method.upper()
243
+ # Default to LQA if method is not recognized
244
+ if method_key not in method_map:
245
+ print(f"Unexpected method '{self.method}'. (default method is LQA.)")
246
+ return LQA
247
+
248
+ return method_map[method_key]
249
+
250
+ def _run_single_irc(self, directory, initial_geometry):
251
+ """Run a single IRC calculation
252
+
253
+ Parameters
254
+ ----------
255
+ directory : str
256
+ Directory to store results
257
+ initial_geometry : numpy.ndarray
258
+ Initial geometry coordinates
259
+
260
+ Returns
261
+ -------
262
+ object
263
+ IRC method instance
264
+ """
265
+
266
+
267
+ MethodClass = self._get_irc_method_class()
268
+
269
+ kill_inds = None # Initialize kill_inds
270
+ if len(self.method_options) > 0 and MethodClass == ModeKill:
271
+ # Pass kill_inds if specified in method options
272
+ try:
273
+ kill_inds = [int(idx) for idx in self.method_options[0].split(",")]
274
+ print(f"Using specified kill_inds: {kill_inds}")
275
+ except ValueError:
276
+ print("Invalid kill_inds format. It should be a comma-separated list of integers.")
277
+ kill_inds = None
278
+
279
+ irc_instance = MethodClass(
280
+ self.element_list,
281
+ self.electric_charge_and_multiplicity,
282
+ self.FC_count,
283
+ directory,
284
+ self.final_directory,
285
+ self.force_data,
286
+ max_step=self.max_step,
287
+ step_size=self.step_size,
288
+ init_coord=initial_geometry,
289
+ init_hess=self.hessian,
290
+ calc_engine=self.QM_interface,
291
+ xtb_method=self.xtb_method,
292
+ kill_inds=kill_inds
293
+ )
294
+
295
+ irc_instance.run()
296
+ return irc_instance
297
+
298
+ def _run_irc(self):
299
+ """Run IRC calculation in both forward and backward directions"""
300
+ # Forward direction (from TS to products)
301
+ print("Forward IRC")
302
+ init_geom = self.geom_num_list + self.initial_step
303
+
304
+ # Create forward direction directory
305
+ fwd_dir = os.path.join(self.file_directory, "irc_forward")
306
+ os.makedirs(fwd_dir, exist_ok=True)
307
+
308
+ # Run forward IRC
309
+ self._run_single_irc(fwd_dir, init_geom)
310
+
311
+ # Backward direction (from TS to reactants)
312
+ print("Backward IRC")
313
+ init_geom = self.geom_num_list - self.initial_step
314
+
315
+ # Create backward direction directory
316
+ bwd_dir = os.path.join(self.file_directory, "irc_backward")
317
+ os.makedirs(bwd_dir, exist_ok=True)
318
+
319
+ # Run backward IRC
320
+ self._run_single_irc(bwd_dir, init_geom)
321
+
322
+ # Combine XYZ files from forward and backward directions
323
+ self.combine_xyz_files(fwd_dir, bwd_dir)
324
+
325
+ # Combine forward and backward CSV data into a single file
326
+ self.combine_csv_data(fwd_dir, bwd_dir)
327
+
328
+ def _run_meta_irc(self):
329
+ """Run meta-IRC calculation in a single direction"""
330
+ init_geom = self.geom_num_list - self.initial_step
331
+ self._run_single_irc(self.file_directory, init_geom)
332
+
333
+ def calc_IRCpath(self):
334
+ """Calculate IRC path in forward and/or backward directions"""
335
+ print("IRC carry out...")
336
+
337
+ if self.IRC_flag:
338
+ self._run_irc()
339
+ else:
340
+ self._run_meta_irc()
341
+ # --- Added for terminal structure output (meta-IRC) ---
342
+ meta_irc_xyz_file = os.path.join(self.file_directory, "irc_structures.xyz")
343
+ if os.path.exists(meta_irc_xyz_file):
344
+ try:
345
+ meta_irc_geometry_list, _, _ = traj2list(meta_irc_xyz_file, [0, 1])
346
+ if meta_irc_geometry_list: # Check if list is not empty
347
+ terminal_geom_meta = meta_irc_geometry_list[-1]
348
+ outfile_name_meta = os.path.join(self.file_directory, f"{self.fin_xyz_base}_irc_endpoint_1.xyz")
349
+ self.write_xyz(outfile_name_meta, terminal_geom_meta)
350
+ self.terminal_struct_paths = [os.path.abspath(outfile_name_meta)]
351
+ print(f"Meta-IRC terminal structure saved to {outfile_name_meta}")
352
+ except Exception as e:
353
+ print(f"Error processing meta-IRC terminal structure: {e}")
354
+ # --- End of addition ---
355
+
356
+ return
357
+
358
+ def combine_csv_data(self, fwd_dir, bwd_dir):
359
+ """Combine forward and backward CSV data into a single file
360
+
361
+ Parameters
362
+ ----------
363
+ fwd_dir : str
364
+ Forward directory path
365
+ bwd_dir : str
366
+ Backward directory path
367
+ """
368
+ # Final CSV file
369
+ combined_csv = os.path.join(self.file_directory, "irc_combined_data.csv")
370
+
371
+ # Read backward data (to be reversed)
372
+ bwd_data = []
373
+ bwd_csv_path = os.path.join(bwd_dir, "irc_energies_gradients.csv")
374
+ if os.path.exists(bwd_csv_path):
375
+ with open(bwd_csv_path, 'r') as csvfile:
376
+ reader = csv.reader(csvfile)
377
+ try:
378
+ next(reader) # Skip header
379
+ for row in reader:
380
+ bwd_data.append(row)
381
+ except StopIteration:
382
+ print("Warning: Backward CSV file is empty.")
383
+ else:
384
+ print(f"Warning: Backward CSV file not found at {bwd_csv_path}")
385
+
386
+ # Read forward data
387
+ fwd_data = []
388
+ fwd_csv_path = os.path.join(fwd_dir, "irc_energies_gradients.csv")
389
+ if os.path.exists(fwd_csv_path):
390
+ with open(fwd_csv_path, 'r') as csvfile:
391
+ reader = csv.reader(csvfile)
392
+ try:
393
+ next(reader) # Skip header
394
+ for row in reader:
395
+ fwd_data.append(row)
396
+ except StopIteration:
397
+ print("Warning: Forward CSV file is empty.")
398
+ else:
399
+ print(f"Warning: Forward CSV file not found at {fwd_csv_path}")
400
+
401
+ # Prepare TS point data
402
+ ts_data = [0, self.init_e, self.init_B_e, np.sqrt((self.init_g**2).mean()), np.sqrt((self.init_B_g**2).mean())]
403
+
404
+ # Write combined data
405
+ with open(combined_csv, 'w', newline='') as csvfile:
406
+ writer = csv.writer(csvfile)
407
+ writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)', 'RMS Gradient', 'RMS Bias Gradient'])
408
+
409
+ # Write reversed backward data with negative step numbers
410
+ for i, row in enumerate(reversed(bwd_data)):
411
+ try:
412
+ step = -int(row[0])
413
+ writer.writerow([step, row[1], row[2], row[3], row[4]])
414
+ except (IndexError, ValueError) as e:
415
+ print(f"Warning: Skipping malformed row in backward CSV data: {row} ({e})")
416
+
417
+ # Write TS point (step 0)
418
+ writer.writerow(ts_data)
419
+
420
+ # Write forward data with positive step numbers
421
+ for row in fwd_data:
422
+ try:
423
+ writer.writerow(row)
424
+ except (IndexError, ValueError) as e:
425
+ print(f"Warning: Skipping malformed row in forward CSV data: {row} ({e})")
426
+
427
+ print(f"Combined IRC data saved to {combined_csv}")
428
+
429
+ def combine_xyz_files(self, fwd_dir, bwd_dir):
430
+ """Combine forward and backward XYZ structures into a single XYZ file
431
+ and output terminal structures.
432
+
433
+ Parameters
434
+ ----------
435
+ fwd_dir : str
436
+ Forward directory path
437
+ bwd_dir : str
438
+ Backward directory path
439
+ """
440
+ # Path for combined XYZ file
441
+ combined_xyz = os.path.join(self.file_directory, "irc_combined_path.xyz")
442
+
443
+ # Find all XYZ files in forward and backward directories
444
+ fwd_xyz_file = os.path.join(fwd_dir, "irc_structures.xyz")
445
+ bwd_xyz_file = os.path.join(bwd_dir, "irc_structures.xyz")
446
+
447
+ fwd_irc_geometry_list = []
448
+ if os.path.exists(fwd_xyz_file):
449
+ try:
450
+ fwd_irc_geometry_list, _, _ = traj2list(fwd_xyz_file, [0, 1])
451
+ except Exception as e:
452
+ print(f"Error reading forward XYZ file: {e}")
453
+ else:
454
+ print(f"Warning: Forward XYZ file not found at {fwd_xyz_file}")
455
+
456
+ bwd_irc_geometry_list = []
457
+ if os.path.exists(bwd_xyz_file):
458
+ try:
459
+ bwd_irc_geometry_list, _, _ = traj2list(bwd_xyz_file, [0, 1])
460
+ except Exception as e:
461
+ print(f"Error reading backward XYZ file: {e}")
462
+ else:
463
+ print(f"Warning: Backward XYZ file not found at {bwd_xyz_file}")
464
+
465
+
466
+
467
+ self.terminal_struct_paths = [] # Clear any previous paths
468
+ if fwd_irc_geometry_list: # Check if list is not empty
469
+ terminal_geom_fwd = fwd_irc_geometry_list[-1]
470
+ outfile_name_fwd = os.path.join(self.file_directory, f"{self.fin_xyz_base}_irc_endpoint_1.xyz")
471
+ self.write_xyz(outfile_name_fwd, terminal_geom_fwd)
472
+ self.terminal_struct_paths.append(os.path.abspath(outfile_name_fwd))
473
+ print(f"Forward terminal structure saved to {outfile_name_fwd}")
474
+
475
+ if bwd_irc_geometry_list: # Check if list is not empty
476
+ terminal_geom_bwd = bwd_irc_geometry_list[-1]
477
+ outfile_name_bwd = os.path.join(self.file_directory, f"{self.fin_xyz_base}_irc_endpoint_2.xyz")
478
+ self.write_xyz(outfile_name_bwd, terminal_geom_bwd)
479
+ self.terminal_struct_paths.append(os.path.abspath(outfile_name_bwd))
480
+ print(f"Backward terminal structure saved to {outfile_name_bwd}")
481
+
482
+
483
+ fwd_irc_geometry_list = fwd_irc_geometry_list[::-1] # Reverse forward list
484
+
485
+ # TS structure from the final directory
486
+ ts_xyz_file_path = glob.glob(os.path.join(self.final_directory, "*.xyz"))
487
+
488
+ # Write combined XYZ file
489
+ with open(combined_xyz, 'w') as outfile:
490
+ # Forward structures (from last to first)
491
+ for xyz in fwd_irc_geometry_list:
492
+ outfile.write(f"{len(xyz)}\n")
493
+ outfile.write("\n")
494
+ for i in range(len(xyz)):
495
+ outfile.write(self.element_list[i] + " " + " ".join(map(str, xyz[i])) + "\n")
496
+
497
+ # TS structure
498
+ if ts_xyz_file_path:
499
+ with open(ts_xyz_file_path[0], 'r') as infile:
500
+ outfile.write(infile.read())
501
+ else:
502
+ print(f"Warning: TS XYZ file not found in {self.final_directory}")
503
+
504
+ # Backward structures (from first to last)
505
+ for xyz in bwd_irc_geometry_list:
506
+ outfile.write(f"{len(xyz)}\n")
507
+ outfile.write("\n")
508
+ for i in range(len(xyz)):
509
+ outfile.write(self.element_list[i] + " " + " ".join(map(str, xyz[i])) + "\n")
510
+
511
+
512
+ print(f"Combined IRC path saved to {combined_xyz}")
513
+
514
+ def run(self):
515
+ """Main function to run IRC calculation"""
516
+ # Check if starting point is a saddle point and get initial displacement
517
+ self.initial_step, self.IRC_flag, self.geom_num_list, finish_flag = self.saddle_check()
518
+
519
+ if finish_flag:
520
+ print("IRC calculation failed.")
521
+ self.terminal_struct_paths = []
522
+ return
523
+
524
+ # Calculate the IRC path
525
+ self.calc_IRCpath()
526
+
527
+ print("IRC calculation is finished.")
528
+ # self.terminal_struct_paths can be accessed for terminal structure file paths.
529
+ return