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/rk4.py ADDED
@@ -0,0 +1,579 @@
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
+
15
+ class RK4:
16
+ """Runge-Kutta 4th order method for IRC calculations
17
+
18
+ References
19
+ ----------
20
+ [1] J. Chem. Phys. 95, 9, 6758–6763 (1991)
21
+ [2] Chem. Phys. Lett. 437, 1–3, 120-125 (2007)
22
+ """
23
+
24
+ def __init__(self, element_list, electric_charge_and_multiplicity, FC_count, file_directory,
25
+ final_directory, force_data, max_step=1000, step_size=0.1, init_coord=None,
26
+ init_hess=None, calc_engine=None, xtb_method=None, **kwargs):
27
+ """Initialize RK4 IRC calculator
28
+
29
+ Parameters
30
+ ----------
31
+ element_list : list
32
+ List of atomic elements
33
+ electric_charge_and_multiplicity : tuple
34
+ Charge and multiplicity for the system
35
+ FC_count : int
36
+ Frequency of full hessian recalculation
37
+ file_directory : str
38
+ Working directory
39
+ final_directory : str
40
+ Directory for final output
41
+ force_data : dict
42
+ Force field data for bias potential
43
+ max_step : int, optional
44
+ Maximum number of steps
45
+ step_size : float, optional
46
+ Step size for the IRC
47
+ init_coord : numpy.ndarray, optional
48
+ Initial coordinates
49
+ init_hess : numpy.ndarray, optional
50
+ Initial hessian
51
+ calc_engine : object, optional
52
+ Calculator engine
53
+ xtb_method : str, optional
54
+ XTB method specification
55
+ """
56
+ self.max_step = max_step
57
+ self.step_size = step_size
58
+ self.ModelHessianUpdate = ModelHessianUpdate()
59
+ self.CE = calc_engine
60
+ self.FC_count = FC_count
61
+
62
+ # initial condition
63
+ self.coords = init_coord
64
+ self.init_hess = init_hess
65
+ self.mw_hessian = init_hess # Mass-weighted hessian
66
+ self.xtb_method = xtb_method
67
+
68
+ # convergence criteria
69
+ self.MAX_FORCE_THRESHOLD = 0.0004
70
+ self.RMS_FORCE_THRESHOLD = 0.0001
71
+
72
+ self.element_list = element_list
73
+ self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
74
+ self.directory = file_directory
75
+ self.final_directory = final_directory
76
+ self.force_data = force_data
77
+
78
+ # IRC data storage for current calculation (needed for immediate operations)
79
+ # These will no longer store the full trajectory but only recent points needed for calculations
80
+ self.irc_bias_energy_list = []
81
+ self.irc_energy_list = []
82
+ self.irc_mw_coords = []
83
+ self.irc_mw_gradients = []
84
+ self.irc_mw_bias_gradients = []
85
+ self.path_bending_angle_list = []
86
+
87
+ # Create data files
88
+ self.create_csv_file()
89
+ self.create_xyz_file()
90
+
91
+ def create_csv_file(self):
92
+ """Create CSV file for energy and gradient data"""
93
+ self.csv_filename = os.path.join(self.directory, "irc_energies_gradients.csv")
94
+ with open(self.csv_filename, 'w', newline='') as csvfile:
95
+ writer = csv.writer(csvfile)
96
+ writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)', 'RMS Gradient', 'RMS Bias Gradient'])
97
+
98
+ def create_xyz_file(self):
99
+ """Create XYZ file for structure data"""
100
+ self.xyz_filename = os.path.join(self.directory, "irc_structures.xyz")
101
+ # Create empty file (will be appended to later)
102
+ open(self.xyz_filename, 'w').close()
103
+
104
+ def save_to_csv(self, step, energy, bias_energy, gradient, bias_gradient):
105
+ """Save energy and gradient data to CSV file
106
+
107
+ Parameters
108
+ ----------
109
+ step : int
110
+ Current step number
111
+ energy : float
112
+ Energy in Hartree
113
+ bias_energy : float
114
+ Bias energy in Hartree
115
+ gradient : numpy.ndarray
116
+ Gradient array
117
+ bias_gradient : numpy.ndarray
118
+ Bias gradient array
119
+ """
120
+ rms_grad = np.sqrt((gradient**2).mean())
121
+ rms_bias_grad = np.sqrt((bias_gradient**2).mean())
122
+
123
+ with open(self.csv_filename, 'a', newline='') as csvfile:
124
+ writer = csv.writer(csvfile)
125
+ writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad])
126
+
127
+ def save_xyz_structure(self, step, coords):
128
+ """Save molecular structure to XYZ file
129
+
130
+ Parameters
131
+ ----------
132
+ step : int
133
+ Current step number
134
+ coords : numpy.ndarray
135
+ Atomic coordinates in Bohr
136
+ """
137
+ # Convert coordinates to Angstroms
138
+ coords_angstrom = coords * UnitValueLib().bohr2angstroms
139
+
140
+ with open(self.xyz_filename, 'a') as f:
141
+ # Number of atoms and comment line
142
+ f.write(f"{len(coords)}\n")
143
+ f.write(f"IRC Step {step}\n")
144
+
145
+ # Atomic coordinates
146
+ for i, coord in enumerate(coords_angstrom):
147
+ f.write(f"{self.element_list[i]:<3} {coord[0]:15.10f} {coord[1]:15.10f} {coord[2]:15.10f}\n")
148
+
149
+ def get_mass_array(self):
150
+ """Create arrays of atomic masses for mass-weighting operations"""
151
+ elem_mass_list = np.array([atomic_mass(elem) for elem in self.element_list], dtype="float64")
152
+ sqrt_mass_list = np.sqrt(elem_mass_list)
153
+
154
+ # Create arrays for 3D operations (x,y,z for each atom)
155
+ three_elem_mass_list = np.repeat(elem_mass_list, 3)
156
+ three_sqrt_mass_list = np.repeat(sqrt_mass_list, 3)
157
+
158
+ return elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list
159
+
160
+ def mass_weight_hessian(self, hessian, three_sqrt_mass_list):
161
+ """Apply mass-weighting to the hessian matrix
162
+
163
+ Parameters
164
+ ----------
165
+ hessian : numpy.ndarray
166
+ Hessian matrix in non-mass-weighted coordinates
167
+ three_sqrt_mass_list : numpy.ndarray
168
+ Array of sqrt(mass) values repeated for x,y,z per atom
169
+
170
+ Returns
171
+ -------
172
+ numpy.ndarray
173
+ Mass-weighted hessian
174
+ """
175
+ mass_mat = np.diag(1.0 / three_sqrt_mass_list)
176
+ return np.dot(mass_mat, np.dot(hessian, mass_mat))
177
+
178
+ def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
179
+ """Convert coordinates to mass-weighted coordinates
180
+
181
+ Parameters
182
+ ----------
183
+ coordinates : numpy.ndarray
184
+ Coordinates in non-mass-weighted form
185
+ sqrt_mass_list : numpy.ndarray
186
+ Array of sqrt(mass) values for each atom
187
+
188
+ Returns
189
+ -------
190
+ numpy.ndarray
191
+ Mass-weighted coordinates
192
+ """
193
+ mw_coords = copy.deepcopy(coordinates)
194
+ for i in range(len(coordinates)):
195
+ mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
196
+ return mw_coords
197
+
198
+ def mass_weight_gradient(self, gradient, sqrt_mass_list):
199
+ """Convert gradient to mass-weighted form
200
+
201
+ Parameters
202
+ ----------
203
+ gradient : numpy.ndarray
204
+ Gradient in non-mass-weighted form
205
+ sqrt_mass_list : numpy.ndarray
206
+ Array of sqrt(mass) values for each atom
207
+
208
+ Returns
209
+ -------
210
+ numpy.ndarray
211
+ Mass-weighted gradient
212
+ """
213
+ mw_gradient = copy.deepcopy(gradient)
214
+ for i in range(len(gradient)):
215
+ mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
216
+ return mw_gradient
217
+
218
+ def unmass_weight_step(self, step, sqrt_mass_list):
219
+ """Convert a step vector from mass-weighted to non-mass-weighted coordinates
220
+
221
+ Parameters
222
+ ----------
223
+ step : numpy.ndarray
224
+ Step in mass-weighted form
225
+ sqrt_mass_list : numpy.ndarray
226
+ Array of sqrt(mass) values for each atom
227
+
228
+ Returns
229
+ -------
230
+ numpy.ndarray
231
+ Step in non-mass-weighted coordinates
232
+ """
233
+ unmw_step = copy.deepcopy(step)
234
+ for i in range(len(step)):
235
+ unmw_step[i] = step[i] / sqrt_mass_list[i]
236
+ return unmw_step
237
+
238
+ def check_energy_oscillation(self, energy_list):
239
+ """Check if energy is oscillating (going up and down)
240
+
241
+ Parameters
242
+ ----------
243
+ energy_list : list
244
+ List of energy values
245
+
246
+ Returns
247
+ -------
248
+ bool
249
+ True if energy is oscillating, False otherwise
250
+ """
251
+ if len(energy_list) < 3:
252
+ return False
253
+
254
+ # Check if the energy changes direction (from increasing to decreasing or vice versa)
255
+ last_diff = energy_list[-1] - energy_list[-2]
256
+ prev_diff = energy_list[-2] - energy_list[-3]
257
+
258
+ # Return True if the energy direction has changed
259
+ return (last_diff * prev_diff) < 0
260
+
261
+ def get_k(self, mw_coords, mw_gradient):
262
+ """Calculate k value for RK4 integration
263
+
264
+ Parameters
265
+ ----------
266
+ mw_coords : numpy.ndarray
267
+ Mass-weighted coordinates
268
+ mw_gradient : numpy.ndarray
269
+ Mass-weighted gradient
270
+
271
+ Returns
272
+ -------
273
+ numpy.ndarray
274
+ k vector for RK4 step
275
+ """
276
+ # Flatten gradient for norm calculation
277
+ flat_gradient = mw_gradient.flatten()
278
+ norm = np.linalg.norm(flat_gradient)
279
+
280
+ # Avoid division by zero
281
+ if norm < 1e-12:
282
+ direction = np.zeros_like(mw_gradient)
283
+ else:
284
+ direction = -mw_gradient / norm
285
+
286
+ # Return step scaled by step_size
287
+ return self.step_size * direction
288
+
289
+ def step(self, mw_gradient, geom_num_list, mw_BPA_hessian, sqrt_mass_list):
290
+ """Calculate a single RK4 IRC step
291
+
292
+ Parameters
293
+ ----------
294
+ mw_gradient : numpy.ndarray
295
+ Mass-weighted gradient
296
+ geom_num_list : numpy.ndarray
297
+ Current geometry coordinates
298
+ mw_BPA_hessian : numpy.ndarray
299
+ Mass-weighted bias potential hessian
300
+ sqrt_mass_list : numpy.ndarray
301
+ Array of sqrt(mass) values for each atom
302
+
303
+ Returns
304
+ -------
305
+ numpy.ndarray
306
+ New geometry coordinates
307
+ """
308
+ # Update Hessian if we have previous points
309
+ if len(self.irc_mw_gradients) > 1 and len(self.irc_mw_coords) > 1:
310
+ delta_g = (self.irc_mw_gradients[-1] - self.irc_mw_gradients[-2]).reshape(-1, 1)
311
+ delta_x = (self.irc_mw_coords[-1] - self.irc_mw_coords[-2]).reshape(-1, 1)
312
+
313
+ # Only update if the step and gradient difference are meaningful
314
+ if np.dot(delta_x.T, delta_g)[0, 0] > 1e-10:
315
+ delta_hess = self.ModelHessianUpdate.BFGS_hessian_update(self.mw_hessian, delta_x, delta_g)
316
+ self.mw_hessian += delta_hess
317
+
318
+ # Add bias potential hessian to Hessian
319
+ combined_hessian = self.mw_hessian + mw_BPA_hessian
320
+
321
+ # Mass-weight the current coordinates
322
+ mw_coords = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
323
+
324
+ # Runge-Kutta 4th order integration
325
+ # k1 calculation
326
+ k1 = self.get_k(mw_coords, mw_gradient)
327
+
328
+ # k2 calculation - we need to evaluate gradient at coords + 0.5*k1
329
+ # This requires a new energy/gradient calculation at this point
330
+ mw_coords_k2 = mw_coords + 0.5 * k1
331
+ coords_k2 = self.unmass_weight_step(mw_coords_k2, sqrt_mass_list)
332
+
333
+ e_k2, g_k2, _, _ = self.CE.single_point(
334
+ self.final_directory,
335
+ self.element_list,
336
+ -1, # Temporary calculation, no step number
337
+ self.electric_charge_and_multiplicity,
338
+ self.xtb_method,
339
+ UnitValueLib().bohr2angstroms * coords_k2
340
+ )
341
+
342
+ # Calculate bias potential at k2 point
343
+ CalcBiaspot = BiasPotentialCalculation(self.directory)
344
+ _, _, B_g_k2, _ = CalcBiaspot.main(
345
+ e_k2, g_k2, coords_k2, self.element_list,
346
+ self.force_data, g_k2, -1, coords_k2
347
+ )
348
+
349
+ # Mass-weight k2 gradient
350
+ mw_B_g_k2 = self.mass_weight_gradient(B_g_k2, sqrt_mass_list)
351
+ k2 = self.get_k(mw_coords_k2, mw_B_g_k2)
352
+
353
+ # k3 calculation
354
+ mw_coords_k3 = mw_coords + 0.5 * k2
355
+ coords_k3 = self.unmass_weight_step(mw_coords_k3, sqrt_mass_list)
356
+
357
+ e_k3, g_k3, _, _ = self.CE.single_point(
358
+ self.final_directory,
359
+ self.element_list,
360
+ -1, # Temporary calculation, no step number
361
+ self.electric_charge_and_multiplicity,
362
+ self.xtb_method,
363
+ UnitValueLib().bohr2angstroms * coords_k3
364
+ )
365
+
366
+ # Calculate bias potential at k3 point
367
+ _, _, B_g_k3, _ = CalcBiaspot.main(
368
+ e_k3, g_k3, coords_k3, self.element_list,
369
+ self.force_data, g_k3, -1, coords_k3
370
+ )
371
+
372
+ # Mass-weight k3 gradient
373
+ mw_B_g_k3 = self.mass_weight_gradient(B_g_k3, sqrt_mass_list)
374
+ k3 = self.get_k(mw_coords_k3, mw_B_g_k3)
375
+
376
+ # k4 calculation
377
+ mw_coords_k4 = mw_coords + k3
378
+ coords_k4 = self.unmass_weight_step(mw_coords_k4, sqrt_mass_list)
379
+
380
+ e_k4, g_k4, _, _ = self.CE.single_point(
381
+ self.final_directory,
382
+ self.element_list,
383
+ -1, # Temporary calculation, no step number
384
+ self.electric_charge_and_multiplicity,
385
+ self.xtb_method,
386
+ UnitValueLib().bohr2angstroms * coords_k4
387
+ )
388
+
389
+ # Calculate bias potential at k4 point
390
+ _, _, B_g_k4, _ = CalcBiaspot.main(
391
+ e_k4, g_k4, coords_k4, self.element_list,
392
+ self.force_data, g_k4, -1, coords_k4
393
+ )
394
+
395
+ # Mass-weight k4 gradient
396
+ mw_B_g_k4 = self.mass_weight_gradient(B_g_k4, sqrt_mass_list)
397
+ k4 = self.get_k(mw_coords_k4, mw_B_g_k4)
398
+
399
+ # Calculate step using RK4 formula
400
+ mw_step = (k1 + 2*k2 + 2*k3 + k4) / 6
401
+ step = self.unmass_weight_step(mw_step, sqrt_mass_list)
402
+
403
+ # Update geometry
404
+ new_geom = geom_num_list + step
405
+
406
+ # Remove center of mass motion
407
+ new_geom -= Calculationtools().calc_center_of_mass(new_geom, self.element_list)
408
+
409
+ return new_geom
410
+
411
+ def run(self):
412
+ """Run the RK4 IRC calculation"""
413
+ print("Runge-Kutta 4th Order method")
414
+ geom_num_list = self.coords
415
+ CalcBiaspot = BiasPotentialCalculation(self.directory)
416
+
417
+ # Initialize oscillation counter
418
+ oscillation_counter = 0
419
+
420
+ for iter in range(1, self.max_step):
421
+ print("# STEP: ", iter)
422
+ exit_file_detect = os.path.exists(self.directory+"end.txt")
423
+
424
+ if exit_file_detect:
425
+ break
426
+
427
+ # Calculate energy, gradient and new geometry
428
+ e, g, geom_num_list, finish_frag = self.CE.single_point(
429
+ self.final_directory,
430
+ self.element_list,
431
+ iter,
432
+ self.electric_charge_and_multiplicity,
433
+ self.xtb_method,
434
+ UnitValueLib().bohr2angstroms * geom_num_list
435
+ )
436
+
437
+ # Calculate bias potential
438
+ _, B_e, B_g, BPA_hessian = CalcBiaspot.main(
439
+ e, g, geom_num_list, self.element_list,
440
+ self.force_data, g, iter-1, geom_num_list
441
+ )
442
+
443
+ if finish_frag:
444
+ break
445
+
446
+ # Recalculate Hessian if needed
447
+ if iter % self.FC_count == 0:
448
+ self.mw_hessian = self.CE.Model_hess
449
+ self.mw_hessian = Calculationtools().project_out_hess_tr_and_rot(
450
+ self.mw_hessian, self.element_list, geom_num_list
451
+ )
452
+
453
+ # Get mass arrays for consistent mass-weighting
454
+ elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
455
+
456
+ # Mass-weight the hessian
457
+ mw_BPA_hessian = self.mass_weight_hessian(BPA_hessian, three_sqrt_mass_list)
458
+
459
+ # Mass-weight the coordinates
460
+ mw_geom_num_list = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
461
+
462
+ # Mass-weight the gradients
463
+ mw_g = self.mass_weight_gradient(g, sqrt_mass_list)
464
+ mw_B_g = self.mass_weight_gradient(B_g, sqrt_mass_list)
465
+
466
+ # Save structure to XYZ file
467
+ self.save_xyz_structure(iter, geom_num_list)
468
+
469
+ # Save energy and gradient data to CSV
470
+ self.save_to_csv(iter, e, B_e, g, B_g)
471
+
472
+ # Store IRC data for calculation purposes (limit to keep only necessary data)
473
+ # Keep only last 3 points for calculations like path bending angles and hessian updates
474
+ if len(self.irc_energy_list) >= 3:
475
+ self.irc_energy_list.pop(0)
476
+ self.irc_bias_energy_list.pop(0)
477
+ self.irc_mw_coords.pop(0)
478
+ self.irc_mw_gradients.pop(0)
479
+ self.irc_mw_bias_gradients.pop(0)
480
+
481
+ self.irc_energy_list.append(e)
482
+ self.irc_bias_energy_list.append(B_e)
483
+ self.irc_mw_coords.append(mw_geom_num_list)
484
+ self.irc_mw_gradients.append(mw_g)
485
+ self.irc_mw_bias_gradients.append(mw_B_g)
486
+
487
+ # Check for energy oscillations
488
+ if self.check_energy_oscillation(self.irc_bias_energy_list):
489
+ oscillation_counter += 1
490
+ print(f"Energy oscillation detected ({oscillation_counter}/5)")
491
+
492
+ if oscillation_counter >= 5:
493
+ print("Terminating IRC: Energy oscillated for 5 consecutive steps")
494
+ break
495
+ else:
496
+ # Reset counter if no oscillation is detected
497
+ oscillation_counter = 0
498
+
499
+ if iter > 1:
500
+ # Take RK4 step
501
+ geom_num_list = self.step(
502
+ mw_B_g, geom_num_list, mw_BPA_hessian, sqrt_mass_list
503
+ )
504
+
505
+ # Calculate path bending angle
506
+ if iter > 2:
507
+ bend_angle = Calculationtools().calc_multi_dim_vec_angle(
508
+ self.irc_mw_coords[0]-self.irc_mw_coords[1],
509
+ self.irc_mw_coords[2]-self.irc_mw_coords[1]
510
+ )
511
+ self.path_bending_angle_list.append(np.degrees(bend_angle))
512
+ print("Path bending angle: ", np.degrees(bend_angle))
513
+
514
+ # Check for convergence
515
+ if convergence_check(B_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
516
+ print("Convergence reached. (IRC)")
517
+ break
518
+
519
+ else:
520
+ # First step is simple scaling along the gradient direction
521
+ normalized_grad = mw_B_g / np.linalg.norm(mw_B_g.flatten())
522
+ step = -normalized_grad * self.step_size * 0.05
523
+ step = self.unmass_weight_step(step, sqrt_mass_list)
524
+
525
+ geom_num_list = geom_num_list + step
526
+ geom_num_list -= Calculationtools().calc_center_of_mass(geom_num_list, self.element_list)
527
+
528
+ # Print current geometry
529
+ print()
530
+ for i in range(len(geom_num_list)):
531
+ x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
532
+ y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
533
+ z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
534
+ print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
535
+
536
+ # Display information
537
+ print()
538
+ print("Energy : ", e)
539
+ print("Bias Energy : ", B_e)
540
+ print("RMS B. grad : ", np.sqrt((B_g**2).mean()))
541
+
542
+ if len(self.irc_mw_coords) > 1:
543
+ # Calculate curvature properties
544
+ unit_tangent_vector, curvature_vector, scalar_curvature, curvature_coupling = calc_irc_curvature_properties(
545
+ mw_B_g, self.irc_mw_gradients[-2], self.mw_hessian, self.step_size
546
+ )
547
+
548
+ print("Scalar curvature: ", scalar_curvature)
549
+ print("Curvature coupling: ", curvature_coupling.ravel())
550
+
551
+ # Save curvature properties to file
552
+ save_curvature_properties_to_file(
553
+ os.path.join(self.directory, "irc_curvature_properties.csv"),
554
+ scalar_curvature,
555
+ curvature_coupling
556
+ )
557
+ print()
558
+
559
+
560
+ # Save final data visualization
561
+ G = Graph(self.directory)
562
+ rms_gradient_list = []
563
+ with open(self.csv_filename, 'r') as csvfile:
564
+ reader = csv.reader(csvfile)
565
+ next(reader) # Skip header
566
+ for row in reader:
567
+ rms_gradient_list.append(float(row[3]))
568
+
569
+ G.single_plot(
570
+ np.array(range(len(self.path_bending_angle_list))),
571
+ np.array(self.path_bending_angle_list),
572
+ self.directory,
573
+ atom_num=0,
574
+ axis_name_1="# STEP",
575
+ axis_name_2="bending angle [degrees]",
576
+ name="IRC_bending"
577
+ )
578
+
579
+ return
File without changes