MultiOptPy 1.20.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. multioptpy/Calculator/__init__.py +0 -0
  2. multioptpy/Calculator/ase_calculation_tools.py +424 -0
  3. multioptpy/Calculator/ase_tools/__init__.py +0 -0
  4. multioptpy/Calculator/ase_tools/fairchem.py +28 -0
  5. multioptpy/Calculator/ase_tools/gamess.py +19 -0
  6. multioptpy/Calculator/ase_tools/gaussian.py +165 -0
  7. multioptpy/Calculator/ase_tools/mace.py +28 -0
  8. multioptpy/Calculator/ase_tools/mopac.py +19 -0
  9. multioptpy/Calculator/ase_tools/nwchem.py +31 -0
  10. multioptpy/Calculator/ase_tools/orca.py +22 -0
  11. multioptpy/Calculator/ase_tools/pygfn0.py +37 -0
  12. multioptpy/Calculator/dxtb_calculation_tools.py +344 -0
  13. multioptpy/Calculator/emt_calculation_tools.py +458 -0
  14. multioptpy/Calculator/gpaw_calculation_tools.py +183 -0
  15. multioptpy/Calculator/lj_calculation_tools.py +314 -0
  16. multioptpy/Calculator/psi4_calculation_tools.py +334 -0
  17. multioptpy/Calculator/pwscf_calculation_tools.py +189 -0
  18. multioptpy/Calculator/pyscf_calculation_tools.py +327 -0
  19. multioptpy/Calculator/sqm1_calculation_tools.py +611 -0
  20. multioptpy/Calculator/sqm2_calculation_tools.py +376 -0
  21. multioptpy/Calculator/tblite_calculation_tools.py +352 -0
  22. multioptpy/Calculator/tersoff_calculation_tools.py +818 -0
  23. multioptpy/Constraint/__init__.py +0 -0
  24. multioptpy/Constraint/constraint_condition.py +834 -0
  25. multioptpy/Coordinate/__init__.py +0 -0
  26. multioptpy/Coordinate/polar_coordinate.py +199 -0
  27. multioptpy/Coordinate/redundant_coordinate.py +638 -0
  28. multioptpy/IRC/__init__.py +0 -0
  29. multioptpy/IRC/converge_criteria.py +28 -0
  30. multioptpy/IRC/dvv.py +544 -0
  31. multioptpy/IRC/euler.py +439 -0
  32. multioptpy/IRC/hpc.py +564 -0
  33. multioptpy/IRC/lqa.py +540 -0
  34. multioptpy/IRC/modekill.py +662 -0
  35. multioptpy/IRC/rk4.py +579 -0
  36. multioptpy/Interpolation/__init__.py +0 -0
  37. multioptpy/Interpolation/adaptive_interpolation.py +283 -0
  38. multioptpy/Interpolation/binomial_interpolation.py +179 -0
  39. multioptpy/Interpolation/geodesic_interpolation.py +785 -0
  40. multioptpy/Interpolation/interpolation.py +156 -0
  41. multioptpy/Interpolation/linear_interpolation.py +473 -0
  42. multioptpy/Interpolation/savitzky_golay_interpolation.py +252 -0
  43. multioptpy/Interpolation/spline_interpolation.py +353 -0
  44. multioptpy/MD/__init__.py +0 -0
  45. multioptpy/MD/thermostat.py +185 -0
  46. multioptpy/MEP/__init__.py +0 -0
  47. multioptpy/MEP/pathopt_bneb_force.py +443 -0
  48. multioptpy/MEP/pathopt_dmf_force.py +448 -0
  49. multioptpy/MEP/pathopt_dneb_force.py +130 -0
  50. multioptpy/MEP/pathopt_ewbneb_force.py +207 -0
  51. multioptpy/MEP/pathopt_gpneb_force.py +512 -0
  52. multioptpy/MEP/pathopt_lup_force.py +113 -0
  53. multioptpy/MEP/pathopt_neb_force.py +225 -0
  54. multioptpy/MEP/pathopt_nesb_force.py +205 -0
  55. multioptpy/MEP/pathopt_om_force.py +153 -0
  56. multioptpy/MEP/pathopt_qsm_force.py +174 -0
  57. multioptpy/MEP/pathopt_qsmv2_force.py +304 -0
  58. multioptpy/ModelFunction/__init__.py +7 -0
  59. multioptpy/ModelFunction/avoiding_model_function.py +29 -0
  60. multioptpy/ModelFunction/binary_image_ts_search_model_function.py +47 -0
  61. multioptpy/ModelFunction/conical_model_function.py +26 -0
  62. multioptpy/ModelFunction/opt_meci.py +50 -0
  63. multioptpy/ModelFunction/opt_mesx.py +47 -0
  64. multioptpy/ModelFunction/opt_mesx_2.py +49 -0
  65. multioptpy/ModelFunction/seam_model_function.py +27 -0
  66. multioptpy/ModelHessian/__init__.py +0 -0
  67. multioptpy/ModelHessian/approx_hessian.py +147 -0
  68. multioptpy/ModelHessian/calc_params.py +227 -0
  69. multioptpy/ModelHessian/fischer.py +236 -0
  70. multioptpy/ModelHessian/fischerd3.py +360 -0
  71. multioptpy/ModelHessian/fischerd4.py +398 -0
  72. multioptpy/ModelHessian/gfn0xtb.py +633 -0
  73. multioptpy/ModelHessian/gfnff.py +709 -0
  74. multioptpy/ModelHessian/lindh.py +165 -0
  75. multioptpy/ModelHessian/lindh2007d2.py +707 -0
  76. multioptpy/ModelHessian/lindh2007d3.py +822 -0
  77. multioptpy/ModelHessian/lindh2007d4.py +1030 -0
  78. multioptpy/ModelHessian/morse.py +106 -0
  79. multioptpy/ModelHessian/schlegel.py +144 -0
  80. multioptpy/ModelHessian/schlegeld3.py +322 -0
  81. multioptpy/ModelHessian/schlegeld4.py +559 -0
  82. multioptpy/ModelHessian/shortrange.py +346 -0
  83. multioptpy/ModelHessian/swartd2.py +496 -0
  84. multioptpy/ModelHessian/swartd3.py +706 -0
  85. multioptpy/ModelHessian/swartd4.py +918 -0
  86. multioptpy/ModelHessian/tshess.py +40 -0
  87. multioptpy/Optimizer/QHAdam.py +61 -0
  88. multioptpy/Optimizer/__init__.py +0 -0
  89. multioptpy/Optimizer/abc_fire.py +83 -0
  90. multioptpy/Optimizer/adabelief.py +58 -0
  91. multioptpy/Optimizer/adabound.py +68 -0
  92. multioptpy/Optimizer/adadelta.py +65 -0
  93. multioptpy/Optimizer/adaderivative.py +56 -0
  94. multioptpy/Optimizer/adadiff.py +68 -0
  95. multioptpy/Optimizer/adafactor.py +70 -0
  96. multioptpy/Optimizer/adam.py +65 -0
  97. multioptpy/Optimizer/adamax.py +62 -0
  98. multioptpy/Optimizer/adamod.py +83 -0
  99. multioptpy/Optimizer/adamw.py +65 -0
  100. multioptpy/Optimizer/adiis.py +523 -0
  101. multioptpy/Optimizer/afire_neb.py +282 -0
  102. multioptpy/Optimizer/block_hessian_update.py +709 -0
  103. multioptpy/Optimizer/c2diis.py +491 -0
  104. multioptpy/Optimizer/component_wise_scaling.py +405 -0
  105. multioptpy/Optimizer/conjugate_gradient.py +82 -0
  106. multioptpy/Optimizer/conjugate_gradient_neb.py +345 -0
  107. multioptpy/Optimizer/coordinate_locking.py +405 -0
  108. multioptpy/Optimizer/dic_rsirfo.py +1015 -0
  109. multioptpy/Optimizer/ediis.py +417 -0
  110. multioptpy/Optimizer/eve.py +76 -0
  111. multioptpy/Optimizer/fastadabelief.py +61 -0
  112. multioptpy/Optimizer/fire.py +77 -0
  113. multioptpy/Optimizer/fire2.py +249 -0
  114. multioptpy/Optimizer/fire_neb.py +92 -0
  115. multioptpy/Optimizer/gan_step.py +486 -0
  116. multioptpy/Optimizer/gdiis.py +609 -0
  117. multioptpy/Optimizer/gediis.py +203 -0
  118. multioptpy/Optimizer/geodesic_step.py +433 -0
  119. multioptpy/Optimizer/gpmin.py +633 -0
  120. multioptpy/Optimizer/gpr_step.py +364 -0
  121. multioptpy/Optimizer/gradientdescent.py +78 -0
  122. multioptpy/Optimizer/gradientdescent_neb.py +52 -0
  123. multioptpy/Optimizer/hessian_update.py +433 -0
  124. multioptpy/Optimizer/hybrid_rfo.py +998 -0
  125. multioptpy/Optimizer/kdiis.py +625 -0
  126. multioptpy/Optimizer/lars.py +21 -0
  127. multioptpy/Optimizer/lbfgs.py +253 -0
  128. multioptpy/Optimizer/lbfgs_neb.py +355 -0
  129. multioptpy/Optimizer/linesearch.py +236 -0
  130. multioptpy/Optimizer/lookahead.py +40 -0
  131. multioptpy/Optimizer/nadam.py +64 -0
  132. multioptpy/Optimizer/newton.py +200 -0
  133. multioptpy/Optimizer/prodigy.py +70 -0
  134. multioptpy/Optimizer/purtubation.py +16 -0
  135. multioptpy/Optimizer/quickmin_neb.py +245 -0
  136. multioptpy/Optimizer/radam.py +75 -0
  137. multioptpy/Optimizer/rfo_neb.py +302 -0
  138. multioptpy/Optimizer/ric_rfo.py +842 -0
  139. multioptpy/Optimizer/rl_step.py +627 -0
  140. multioptpy/Optimizer/rmspropgrave.py +65 -0
  141. multioptpy/Optimizer/rsirfo.py +1647 -0
  142. multioptpy/Optimizer/rsprfo.py +1056 -0
  143. multioptpy/Optimizer/sadam.py +60 -0
  144. multioptpy/Optimizer/samsgrad.py +63 -0
  145. multioptpy/Optimizer/tr_lbfgs.py +678 -0
  146. multioptpy/Optimizer/trim.py +273 -0
  147. multioptpy/Optimizer/trust_radius.py +207 -0
  148. multioptpy/Optimizer/trust_radius_neb.py +121 -0
  149. multioptpy/Optimizer/yogi.py +60 -0
  150. multioptpy/OtherMethod/__init__.py +0 -0
  151. multioptpy/OtherMethod/addf.py +1150 -0
  152. multioptpy/OtherMethod/dimer.py +895 -0
  153. multioptpy/OtherMethod/elastic_image_pair.py +629 -0
  154. multioptpy/OtherMethod/modelfunction.py +456 -0
  155. multioptpy/OtherMethod/newton_traj.py +454 -0
  156. multioptpy/OtherMethod/twopshs.py +1095 -0
  157. multioptpy/PESAnalyzer/__init__.py +0 -0
  158. multioptpy/PESAnalyzer/calc_irc_curvature.py +125 -0
  159. multioptpy/PESAnalyzer/cmds_analysis.py +152 -0
  160. multioptpy/PESAnalyzer/koopman_analysis.py +268 -0
  161. multioptpy/PESAnalyzer/pca_analysis.py +314 -0
  162. multioptpy/Parameters/__init__.py +0 -0
  163. multioptpy/Parameters/atomic_mass.py +20 -0
  164. multioptpy/Parameters/atomic_number.py +22 -0
  165. multioptpy/Parameters/covalent_radii.py +44 -0
  166. multioptpy/Parameters/d2.py +61 -0
  167. multioptpy/Parameters/d3.py +63 -0
  168. multioptpy/Parameters/d4.py +103 -0
  169. multioptpy/Parameters/dreiding.py +34 -0
  170. multioptpy/Parameters/gfn0xtb_param.py +137 -0
  171. multioptpy/Parameters/gfnff_param.py +315 -0
  172. multioptpy/Parameters/gnb.py +104 -0
  173. multioptpy/Parameters/parameter.py +22 -0
  174. multioptpy/Parameters/uff.py +72 -0
  175. multioptpy/Parameters/unit_values.py +20 -0
  176. multioptpy/Potential/AFIR_potential.py +55 -0
  177. multioptpy/Potential/LJ_repulsive_potential.py +345 -0
  178. multioptpy/Potential/__init__.py +0 -0
  179. multioptpy/Potential/anharmonic_keep_potential.py +28 -0
  180. multioptpy/Potential/asym_elllipsoidal_potential.py +718 -0
  181. multioptpy/Potential/electrostatic_potential.py +69 -0
  182. multioptpy/Potential/flux_potential.py +30 -0
  183. multioptpy/Potential/gaussian_potential.py +101 -0
  184. multioptpy/Potential/idpp.py +516 -0
  185. multioptpy/Potential/keep_angle_potential.py +146 -0
  186. multioptpy/Potential/keep_dihedral_angle_potential.py +105 -0
  187. multioptpy/Potential/keep_outofplain_angle_potential.py +70 -0
  188. multioptpy/Potential/keep_potential.py +99 -0
  189. multioptpy/Potential/mechano_force_potential.py +74 -0
  190. multioptpy/Potential/nanoreactor_potential.py +52 -0
  191. multioptpy/Potential/potential.py +896 -0
  192. multioptpy/Potential/spacer_model_potential.py +221 -0
  193. multioptpy/Potential/switching_potential.py +258 -0
  194. multioptpy/Potential/universal_potential.py +34 -0
  195. multioptpy/Potential/value_range_potential.py +36 -0
  196. multioptpy/Potential/void_point_potential.py +25 -0
  197. multioptpy/SQM/__init__.py +0 -0
  198. multioptpy/SQM/sqm1/__init__.py +0 -0
  199. multioptpy/SQM/sqm1/sqm1_core.py +1792 -0
  200. multioptpy/SQM/sqm2/__init__.py +0 -0
  201. multioptpy/SQM/sqm2/calc_tools.py +95 -0
  202. multioptpy/SQM/sqm2/sqm2_basis.py +850 -0
  203. multioptpy/SQM/sqm2/sqm2_bond.py +119 -0
  204. multioptpy/SQM/sqm2/sqm2_core.py +303 -0
  205. multioptpy/SQM/sqm2/sqm2_data.py +1229 -0
  206. multioptpy/SQM/sqm2/sqm2_disp.py +65 -0
  207. multioptpy/SQM/sqm2/sqm2_eeq.py +243 -0
  208. multioptpy/SQM/sqm2/sqm2_overlapint.py +704 -0
  209. multioptpy/SQM/sqm2/sqm2_qm.py +578 -0
  210. multioptpy/SQM/sqm2/sqm2_rep.py +66 -0
  211. multioptpy/SQM/sqm2/sqm2_srb.py +70 -0
  212. multioptpy/Thermo/__init__.py +0 -0
  213. multioptpy/Thermo/normal_mode_analyzer.py +865 -0
  214. multioptpy/Utils/__init__.py +0 -0
  215. multioptpy/Utils/bond_connectivity.py +264 -0
  216. multioptpy/Utils/calc_tools.py +884 -0
  217. multioptpy/Utils/oniom.py +96 -0
  218. multioptpy/Utils/pbc.py +48 -0
  219. multioptpy/Utils/riemann_curvature.py +208 -0
  220. multioptpy/Utils/symmetry_analyzer.py +482 -0
  221. multioptpy/Visualization/__init__.py +0 -0
  222. multioptpy/Visualization/visualization.py +156 -0
  223. multioptpy/WFAnalyzer/MO_analysis.py +104 -0
  224. multioptpy/WFAnalyzer/__init__.py +0 -0
  225. multioptpy/Wrapper/__init__.py +0 -0
  226. multioptpy/Wrapper/autots.py +1239 -0
  227. multioptpy/Wrapper/ieip_wrapper.py +93 -0
  228. multioptpy/Wrapper/md_wrapper.py +92 -0
  229. multioptpy/Wrapper/neb_wrapper.py +94 -0
  230. multioptpy/Wrapper/optimize_wrapper.py +76 -0
  231. multioptpy/__init__.py +5 -0
  232. multioptpy/entrypoints.py +916 -0
  233. multioptpy/fileio.py +660 -0
  234. multioptpy/ieip.py +340 -0
  235. multioptpy/interface.py +1086 -0
  236. multioptpy/irc.py +529 -0
  237. multioptpy/moleculardynamics.py +432 -0
  238. multioptpy/neb.py +1267 -0
  239. multioptpy/optimization.py +1553 -0
  240. multioptpy/optimizer.py +709 -0
  241. multioptpy-1.20.2.dist-info/METADATA +438 -0
  242. multioptpy-1.20.2.dist-info/RECORD +246 -0
  243. multioptpy-1.20.2.dist-info/WHEEL +5 -0
  244. multioptpy-1.20.2.dist-info/entry_points.txt +9 -0
  245. multioptpy-1.20.2.dist-info/licenses/LICENSE +674 -0
  246. multioptpy-1.20.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,439 @@
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 Euler:
16
+ """Euler method for IRC calculations
17
+
18
+ A simple implementation of the IRC using Euler integration.
19
+ This method takes steps in the direction opposite to the gradient.
20
+ This method is not recommended for production use due to its simplicity
21
+ and potential instability, but serves as a basic example of IRC calculation.
22
+
23
+ """
24
+
25
+ def __init__(self, element_list, electric_charge_and_multiplicity, FC_count, file_directory,
26
+ final_directory, force_data, max_step=1000, step_size=0.01, init_coord=None,
27
+ init_hess=None, calc_engine=None, xtb_method=None, **kwargs):
28
+ """Initialize Euler IRC calculator
29
+
30
+ Parameters
31
+ ----------
32
+ element_list : list
33
+ List of atomic elements
34
+ electric_charge_and_multiplicity : tuple
35
+ Charge and multiplicity for the system
36
+ FC_count : int
37
+ Frequency of full hessian recalculation
38
+ file_directory : str
39
+ Working directory
40
+ final_directory : str
41
+ Directory for final output
42
+ force_data : dict
43
+ Force field data for bias potential
44
+ max_step : int, optional
45
+ Maximum number of steps
46
+ step_size : float, optional
47
+ Step size for the IRC
48
+ init_coord : numpy.ndarray, optional
49
+ Initial coordinates
50
+ init_hess : numpy.ndarray, optional
51
+ Initial hessian
52
+ calc_engine : object, optional
53
+ Calculator engine
54
+ xtb_method : str, optional
55
+ XTB method specification
56
+ """
57
+ self.max_step = max_step
58
+ self.step_size = step_size
59
+ self.ModelHessianUpdate = ModelHessianUpdate()
60
+ self.CE = calc_engine
61
+ self.FC_count = FC_count
62
+
63
+ # initial condition
64
+ self.coords = init_coord
65
+ self.init_hess = init_hess
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
79
+ self.irc_bias_energy_list = []
80
+ self.irc_energy_list = []
81
+ self.irc_mw_coords = []
82
+ self.irc_mw_gradients = []
83
+ self.irc_mw_bias_gradients = []
84
+ self.path_length = 0.0
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', 'Path Length'])
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, path_length):
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
+ path_length : float
120
+ Current IRC path length
121
+ """
122
+ rms_grad = np.sqrt((gradient**2).mean())
123
+ rms_bias_grad = np.sqrt((bias_gradient**2).mean())
124
+
125
+ with open(self.csv_filename, 'a', newline='') as csvfile:
126
+ writer = csv.writer(csvfile)
127
+ writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad, path_length])
128
+
129
+ def save_xyz_structure(self, step, coords):
130
+ """Save molecular structure to XYZ file
131
+
132
+ Parameters
133
+ ----------
134
+ step : int
135
+ Current step number
136
+ coords : numpy.ndarray
137
+ Atomic coordinates in Bohr
138
+ """
139
+ # Convert coordinates to Angstroms
140
+ coords_angstrom = coords * UnitValueLib().bohr2angstroms
141
+
142
+ with open(self.xyz_filename, 'a') as f:
143
+ # Number of atoms and comment line
144
+ f.write(f"{len(coords)}\n")
145
+ f.write(f"IRC Step {step}\n")
146
+
147
+ # Atomic coordinates
148
+ for i, coord in enumerate(coords_angstrom):
149
+ f.write(f"{self.element_list[i]:<3} {coord[0]:15.10f} {coord[1]:15.10f} {coord[2]:15.10f}\n")
150
+
151
+ def get_mass_array(self):
152
+ """Create arrays of atomic masses for mass-weighting operations"""
153
+ elem_mass_list = np.array([atomic_mass(elem) for elem in self.element_list], dtype="float64")
154
+ sqrt_mass_list = np.sqrt(elem_mass_list)
155
+
156
+ # Create arrays for 3D operations (x,y,z for each atom)
157
+ three_elem_mass_list = np.repeat(elem_mass_list, 3)
158
+ three_sqrt_mass_list = np.repeat(sqrt_mass_list, 3)
159
+
160
+ return elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list
161
+
162
+ def mass_weight_hessian(self, hessian, three_sqrt_mass_list):
163
+ """Apply mass-weighting to the hessian matrix
164
+
165
+ Parameters
166
+ ----------
167
+ hessian : numpy.ndarray
168
+ Hessian matrix in non-mass-weighted coordinates
169
+ three_sqrt_mass_list : numpy.ndarray
170
+ Array of sqrt(mass) values repeated for x,y,z per atom
171
+
172
+ Returns
173
+ -------
174
+ numpy.ndarray
175
+ Mass-weighted hessian
176
+ """
177
+ mass_mat = np.diag(1.0 / three_sqrt_mass_list)
178
+ return np.dot(mass_mat, np.dot(hessian, mass_mat))
179
+
180
+ def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
181
+ """Convert coordinates to mass-weighted coordinates
182
+
183
+ Parameters
184
+ ----------
185
+ coordinates : numpy.ndarray
186
+ Coordinates in non-mass-weighted form
187
+ sqrt_mass_list : numpy.ndarray
188
+ Array of sqrt(mass) values for each atom
189
+
190
+ Returns
191
+ -------
192
+ numpy.ndarray
193
+ Mass-weighted coordinates
194
+ """
195
+ mw_coords = copy.deepcopy(coordinates)
196
+ for i in range(len(coordinates)):
197
+ mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
198
+ return mw_coords
199
+
200
+ def mass_weight_gradient(self, gradient, sqrt_mass_list):
201
+ """Convert gradient to mass-weighted form
202
+
203
+ Parameters
204
+ ----------
205
+ gradient : numpy.ndarray
206
+ Gradient in non-mass-weighted form
207
+ sqrt_mass_list : numpy.ndarray
208
+ Array of sqrt(mass) values for each atom
209
+
210
+ Returns
211
+ -------
212
+ numpy.ndarray
213
+ Mass-weighted gradient
214
+ """
215
+ mw_gradient = copy.deepcopy(gradient)
216
+ for i in range(len(gradient)):
217
+ mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
218
+ return mw_gradient
219
+
220
+ def unmass_weight_step(self, step, sqrt_mass_list):
221
+ """Convert a step vector from mass-weighted to non-mass-weighted coordinates
222
+
223
+ Parameters
224
+ ----------
225
+ step : numpy.ndarray
226
+ Step in mass-weighted form
227
+ sqrt_mass_list : numpy.ndarray
228
+ Array of sqrt(mass) values for each atom
229
+
230
+ Returns
231
+ -------
232
+ numpy.ndarray
233
+ Step in non-mass-weighted coordinates
234
+ """
235
+ unmw_step = copy.deepcopy(step)
236
+ for i in range(len(step)):
237
+ unmw_step[i] = step[i] / sqrt_mass_list[i]
238
+ return unmw_step
239
+
240
+ def step(self, mw_gradient):
241
+ """Calculate a single Euler IRC step
242
+
243
+ Parameters
244
+ ----------
245
+ mw_gradient : numpy.ndarray
246
+ Mass-weighted gradient
247
+
248
+ Returns
249
+ -------
250
+ numpy.ndarray
251
+ Step vector in mass-weighted coordinates
252
+ float
253
+ Step length
254
+ """
255
+ # Flatten the gradient for vector operations
256
+ grad = mw_gradient.flatten()
257
+ grad_norm = np.linalg.norm(grad)
258
+
259
+ # Step downhill, against the gradient
260
+ step_direction = -grad / grad_norm
261
+ step = self.step_size * step_direction
262
+
263
+ return step.reshape(mw_gradient.shape), self.step_size
264
+
265
+ def check_energy_oscillation(self, energy_list):
266
+ """Check if energy is oscillating (going up and down)
267
+
268
+ Parameters
269
+ ----------
270
+ energy_list : list
271
+ List of energy values
272
+
273
+ Returns
274
+ -------
275
+ bool
276
+ True if energy is oscillating, False otherwise
277
+ """
278
+ if len(energy_list) < 3:
279
+ return False
280
+
281
+ # Check if the energy changes direction (from increasing to decreasing or vice versa)
282
+ last_diff = energy_list[-1] - energy_list[-2]
283
+ prev_diff = energy_list[-2] - energy_list[-3]
284
+
285
+ # Return True if the energy direction has changed
286
+ return (last_diff * prev_diff) < 0
287
+
288
+ def run(self):
289
+ """Run the Euler IRC calculation"""
290
+ print("Euler method for IRC calculation")
291
+ geom_num_list = self.coords
292
+ CalcBiaspot = BiasPotentialCalculation(self.directory)
293
+
294
+ # Initialize oscillation counter
295
+ oscillation_counter = 0
296
+
297
+ for iter in range(1, self.max_step + 1):
298
+ print("# STEP: ", iter)
299
+ exit_file_detect = os.path.exists(self.directory + "end.txt")
300
+
301
+ if exit_file_detect:
302
+ break
303
+
304
+ # Calculate energy, gradient and new geometry
305
+ e, g, geom_num_list, finish_frag = self.CE.single_point(
306
+ self.final_directory,
307
+ self.element_list,
308
+ iter,
309
+ self.electric_charge_and_multiplicity,
310
+ self.xtb_method,
311
+ UnitValueLib().bohr2angstroms * geom_num_list
312
+ )
313
+
314
+ # Calculate bias potential
315
+ _, B_e, B_g, BPA_hessian = CalcBiaspot.main(
316
+ e, g, geom_num_list, self.element_list,
317
+ self.force_data, g, iter-1, geom_num_list
318
+ )
319
+
320
+ if finish_frag:
321
+ break
322
+
323
+ # Get mass arrays for consistent mass-weighting
324
+ elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
325
+
326
+ # Mass-weight the gradients
327
+ mw_g = self.mass_weight_gradient(g, sqrt_mass_list)
328
+ mw_B_g = self.mass_weight_gradient(B_g, sqrt_mass_list)
329
+
330
+ # Mass-weight the coordinates
331
+ mw_geom_num_list = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
332
+
333
+ # Save structure to XYZ file
334
+ self.save_xyz_structure(iter, geom_num_list)
335
+
336
+ # Save energy and gradient data to CSV
337
+ self.save_to_csv(iter, e, B_e, g, B_g, self.path_length)
338
+
339
+ # Store IRC data for calculation purposes (limit to keep only necessary data)
340
+ # Keep only last 3 points for calculations like path bending angles
341
+ if len(self.irc_energy_list) >= 3:
342
+ self.irc_energy_list.pop(0)
343
+ self.irc_bias_energy_list.pop(0)
344
+ self.irc_mw_coords.pop(0)
345
+ self.irc_mw_gradients.pop(0)
346
+ self.irc_mw_bias_gradients.pop(0)
347
+
348
+ self.irc_energy_list.append(e)
349
+ self.irc_bias_energy_list.append(B_e)
350
+ self.irc_mw_coords.append(mw_geom_num_list)
351
+ self.irc_mw_gradients.append(mw_g)
352
+ self.irc_mw_bias_gradients.append(mw_B_g)
353
+
354
+ # Check for energy oscillations
355
+ if self.check_energy_oscillation(self.irc_bias_energy_list):
356
+ oscillation_counter += 1
357
+ print(f"Energy oscillation detected ({oscillation_counter}/5)")
358
+
359
+ if oscillation_counter >= 5:
360
+ print("Terminating IRC: Energy oscillated for 5 consecutive steps")
361
+ break
362
+ else:
363
+ # Reset counter if no oscillation is detected
364
+ oscillation_counter = 0
365
+
366
+ # Calculate path bending angle
367
+ if len(self.irc_mw_coords) >= 3:
368
+ bend_angle = Calculationtools().calc_multi_dim_vec_angle(
369
+ self.irc_mw_coords[0]-self.irc_mw_coords[1],
370
+ self.irc_mw_coords[2]-self.irc_mw_coords[1]
371
+ )
372
+ self.path_bending_angle_list.append(np.degrees(bend_angle))
373
+ print("Path bending angle: ", np.degrees(bend_angle))
374
+
375
+ # Calculate Euler step
376
+ mw_step, step_length = self.step(mw_B_g)
377
+
378
+ # Convert to non-mass-weighted step
379
+ step = self.unmass_weight_step(mw_step, sqrt_mass_list)
380
+
381
+ # Update geometry
382
+ geom_num_list = geom_num_list + step
383
+
384
+ # Remove center of mass motion
385
+ geom_num_list -= Calculationtools().calc_center_of_mass(geom_num_list, self.element_list)
386
+
387
+ # Update path length
388
+ self.path_length += step_length
389
+
390
+ # Check for convergence
391
+ if convergence_check(B_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
392
+ print("Convergence reached. (IRC)")
393
+ break
394
+
395
+ # Print current geometry
396
+ print()
397
+ for i in range(len(geom_num_list)):
398
+ x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
399
+ y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
400
+ z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
401
+ print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
402
+
403
+ # Display information
404
+ print()
405
+ print("Energy : ", e)
406
+ print("Bias Energy : ", B_e)
407
+ print("RMS B. grad : ", np.sqrt((B_g**2).mean()))
408
+ print("Path length : ", self.path_length)
409
+
410
+ if len(self.irc_mw_coords) > 1:
411
+ # Calculate curvature properties
412
+ unit_tangent_vector, curvature_vector, scalar_curvature, curvature_coupling = calc_irc_curvature_properties(
413
+ mw_B_g, self.irc_mw_gradients[-2], self.mw_hessian, self.step_size
414
+ )
415
+
416
+ print("Scalar curvature: ", scalar_curvature)
417
+ print("Curvature coupling: ", curvature_coupling.ravel())
418
+
419
+ # Save curvature properties to file
420
+ save_curvature_properties_to_file(
421
+ os.path.join(self.directory, "irc_curvature_properties.csv"),
422
+ scalar_curvature,
423
+ curvature_coupling
424
+ )
425
+ print()
426
+
427
+ # Save final data visualization
428
+ G = Graph(self.directory)
429
+ G.single_plot(
430
+ np.array(range(len(self.path_bending_angle_list))),
431
+ np.array(self.path_bending_angle_list),
432
+ self.directory,
433
+ atom_num=0,
434
+ axis_name_1="# STEP",
435
+ axis_name_2="bending angle [degrees]",
436
+ name="IRC_bending"
437
+ )
438
+
439
+ return