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/lqa.py ADDED
@@ -0,0 +1,540 @@
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 LQA:
16
+ """Local quadratic approximation method for IRC calculations
17
+
18
+ References
19
+ ----------
20
+ [1] J. Chem. Phys. 93, 5634–5642 (1990)
21
+ [2] J. Chem. Phys. 120, 9918–9924 (2004)
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 LQA 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.N_euler = 20000 # Number of Euler integration steps
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.mw_hessian = init_hess # Mass-weighted hessian
67
+ self.xtb_method = xtb_method
68
+
69
+ # convergence criteria
70
+ self.MAX_FORCE_THRESHOLD = 0.0004
71
+ self.RMS_FORCE_THRESHOLD = 0.0001
72
+
73
+ self.element_list = element_list
74
+ self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
75
+ self.directory = file_directory
76
+ self.final_directory = final_directory
77
+ self.force_data = force_data
78
+
79
+ # IRC data storage for current calculation (needed for immediate operations)
80
+ # These will no longer store the full trajectory but only recent points needed for calculations
81
+ self.irc_bias_energy_list = []
82
+ self.irc_energy_list = []
83
+ self.irc_mw_coords = []
84
+ self.irc_mw_gradients = []
85
+ self.irc_mw_bias_gradients = []
86
+ self.path_bending_angle_list = []
87
+
88
+ # Create data files
89
+ self.create_csv_file()
90
+ self.create_xyz_file()
91
+
92
+ def create_csv_file(self):
93
+ """Create CSV file for energy and gradient data"""
94
+ self.csv_filename = os.path.join(self.directory, "irc_energies_gradients.csv")
95
+ with open(self.csv_filename, 'w', newline='') as csvfile:
96
+ writer = csv.writer(csvfile)
97
+ writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)', 'RMS Gradient', 'RMS Bias Gradient'])
98
+
99
+ def create_xyz_file(self):
100
+ """Create XYZ file for structure data"""
101
+ self.xyz_filename = os.path.join(self.directory, "irc_structures.xyz")
102
+ # Create empty file (will be appended to later)
103
+ open(self.xyz_filename, 'w').close()
104
+
105
+ def save_to_csv(self, step, energy, bias_energy, gradient, bias_gradient):
106
+ """Save energy and gradient data to CSV file
107
+
108
+ Parameters
109
+ ----------
110
+ step : int
111
+ Current step number
112
+ energy : float
113
+ Energy in Hartree
114
+ bias_energy : float
115
+ Bias energy in Hartree
116
+ gradient : numpy.ndarray
117
+ Gradient array
118
+ bias_gradient : numpy.ndarray
119
+ Bias gradient array
120
+ """
121
+ rms_grad = np.sqrt((gradient**2).mean())
122
+ rms_bias_grad = np.sqrt((bias_gradient**2).mean())
123
+
124
+ with open(self.csv_filename, 'a', newline='') as csvfile:
125
+ writer = csv.writer(csvfile)
126
+ writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad])
127
+
128
+ def save_xyz_structure(self, step, coords):
129
+ """Save molecular structure to XYZ file
130
+
131
+ Parameters
132
+ ----------
133
+ step : int
134
+ Current step number
135
+ coords : numpy.ndarray
136
+ Atomic coordinates in Bohr
137
+ """
138
+ # Convert coordinates to Angstroms
139
+ coords_angstrom = coords * UnitValueLib().bohr2angstroms
140
+
141
+ with open(self.xyz_filename, 'a') as f:
142
+ # Number of atoms and comment line
143
+ f.write(f"{len(coords)}\n")
144
+ f.write(f"IRC Step {step}\n")
145
+
146
+ # Atomic coordinates
147
+ for i, coord in enumerate(coords_angstrom):
148
+ f.write(f"{self.element_list[i]:<3} {coord[0]:15.10f} {coord[1]:15.10f} {coord[2]:15.10f}\n")
149
+
150
+ def get_mass_array(self):
151
+ """Create arrays of atomic masses for mass-weighting operations"""
152
+ elem_mass_list = np.array([atomic_mass(elem) for elem in self.element_list], dtype="float64")
153
+ sqrt_mass_list = np.sqrt(elem_mass_list)
154
+
155
+ # Create arrays for 3D operations (x,y,z for each atom)
156
+ three_elem_mass_list = np.repeat(elem_mass_list, 3)
157
+ three_sqrt_mass_list = np.repeat(sqrt_mass_list, 3)
158
+
159
+ return elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list
160
+
161
+ def mass_weight_hessian(self, hessian, three_sqrt_mass_list):
162
+ """Apply mass-weighting to the hessian matrix
163
+
164
+ Parameters
165
+ ----------
166
+ hessian : numpy.ndarray
167
+ Hessian matrix in non-mass-weighted coordinates
168
+ three_sqrt_mass_list : numpy.ndarray
169
+ Array of sqrt(mass) values repeated for x,y,z per atom
170
+
171
+ Returns
172
+ -------
173
+ numpy.ndarray
174
+ Mass-weighted hessian
175
+ """
176
+ mass_mat = np.diag(1.0 / three_sqrt_mass_list)
177
+ return np.dot(mass_mat, np.dot(hessian, mass_mat))
178
+
179
+ def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
180
+ """Convert coordinates to mass-weighted coordinates
181
+
182
+ Parameters
183
+ ----------
184
+ coordinates : numpy.ndarray
185
+ Coordinates in non-mass-weighted form
186
+ sqrt_mass_list : numpy.ndarray
187
+ Array of sqrt(mass) values for each atom
188
+
189
+ Returns
190
+ -------
191
+ numpy.ndarray
192
+ Mass-weighted coordinates
193
+ """
194
+ mw_coords = copy.deepcopy(coordinates)
195
+ for i in range(len(coordinates)):
196
+ mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
197
+ return mw_coords
198
+
199
+ def mass_weight_gradient(self, gradient, sqrt_mass_list):
200
+ """Convert gradient to mass-weighted form
201
+
202
+ Parameters
203
+ ----------
204
+ gradient : numpy.ndarray
205
+ Gradient in non-mass-weighted form
206
+ sqrt_mass_list : numpy.ndarray
207
+ Array of sqrt(mass) values for each atom
208
+
209
+ Returns
210
+ -------
211
+ numpy.ndarray
212
+ Mass-weighted gradient
213
+ """
214
+ mw_gradient = copy.deepcopy(gradient)
215
+ for i in range(len(gradient)):
216
+ mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
217
+ return mw_gradient
218
+
219
+ def unmass_weight_step(self, step, sqrt_mass_list):
220
+ """Convert a step vector from mass-weighted to non-mass-weighted coordinates
221
+
222
+ Parameters
223
+ ----------
224
+ step : numpy.ndarray
225
+ Step in mass-weighted form
226
+ sqrt_mass_list : numpy.ndarray
227
+ Array of sqrt(mass) values for each atom
228
+
229
+ Returns
230
+ -------
231
+ numpy.ndarray
232
+ Step in non-mass-weighted coordinates
233
+ """
234
+ unmw_step = copy.deepcopy(step)
235
+ for i in range(len(step)):
236
+ unmw_step[i] = step[i] / sqrt_mass_list[i]
237
+ return unmw_step
238
+
239
+ def check_energy_oscillation(self, energy_list):
240
+ """Check if energy is oscillating (going up and down)
241
+
242
+ Parameters
243
+ ----------
244
+ energy_list : list
245
+ List of energy values
246
+
247
+ Returns
248
+ -------
249
+ bool
250
+ True if energy is oscillating, False otherwise
251
+ """
252
+ if len(energy_list) < 3:
253
+ return False
254
+
255
+ # Check if the energy changes direction (from increasing to decreasing or vice versa)
256
+ last_diff = energy_list[-1] - energy_list[-2]
257
+ prev_diff = energy_list[-2] - energy_list[-3]
258
+
259
+ # Return True if the energy direction has changed
260
+ return (last_diff * prev_diff) < 0
261
+
262
+ def step(self, mw_gradient, geom_num_list, mw_BPA_hessian, sqrt_mass_list):
263
+ """Calculate a single LQA IRC step
264
+
265
+ Parameters
266
+ ----------
267
+ mw_gradient : numpy.ndarray
268
+ Mass-weighted gradient
269
+ geom_num_list : numpy.ndarray
270
+ Current geometry coordinates
271
+ mw_BPA_hessian : numpy.ndarray
272
+ Mass-weighted bias potential hessian
273
+ sqrt_mass_list : numpy.ndarray
274
+ Array of sqrt(mass) values for each atom
275
+
276
+ Returns
277
+ -------
278
+ numpy.ndarray
279
+ New geometry coordinates
280
+ """
281
+ # Update Hessian if we have previous points
282
+ if len(self.irc_mw_gradients) > 1 and len(self.irc_mw_coords) > 1:
283
+ delta_g = (self.irc_mw_gradients[-1] - self.irc_mw_gradients[-2]).reshape(-1, 1)
284
+ delta_x = (self.irc_mw_coords[-1] - self.irc_mw_coords[-2]).reshape(-1, 1)
285
+
286
+ # Only update if the step and gradient difference are meaningful
287
+ if np.dot(delta_x.T, delta_g)[0, 0] > 1e-10:
288
+ delta_hess = self.ModelHessianUpdate.BFGS_hessian_update(self.mw_hessian, delta_x, delta_g)
289
+ self.mw_hessian += delta_hess
290
+
291
+ # Add bias potential hessian and diagonalize
292
+ combined_hessian = self.mw_hessian + mw_BPA_hessian
293
+ eigenvalues, eigenvectors = np.linalg.eigh(combined_hessian)
294
+
295
+ # Drop small eigenvalues and corresponding eigenvectors
296
+ small_eigvals = np.abs(eigenvalues) < 1e-8
297
+ eigenvalues = eigenvalues[~small_eigvals]
298
+ eigenvectors = eigenvectors[:,~small_eigvals]
299
+
300
+ # Reshape gradient for matrix operations
301
+ flattened_gradient = mw_gradient.flatten()
302
+
303
+ # --- START MODIFICATION (Fix for numerical stability) ---
304
+
305
+ # Time step for numerical integration
306
+ # Original: dt = 1 / self.N_euler * self.step_size / np.linalg.norm(flattened_gradient)
307
+ # This can diverge if np.linalg.norm(flattened_gradient) -> 0
308
+
309
+ epsilon = 1e-6 # Prevent divergence when gradient norm is near zero
310
+ norm_g = np.linalg.norm(flattened_gradient)
311
+ dt = 1 / self.N_euler * self.step_size / max(norm_g, epsilon)
312
+
313
+ # --- END MODIFICATION ---
314
+
315
+ # Transform gradient to eigensystem of the hessian
316
+ mw_gradient_proj = np.dot(eigenvectors.T, flattened_gradient)
317
+
318
+ # Integration of the step size
319
+ t = dt
320
+ current_length = 0
321
+ for j in range(self.N_euler):
322
+ dsdt = np.sqrt(np.sum(mw_gradient_proj**2 * np.exp(-2*eigenvalues*t)))
323
+ current_length += dsdt * dt
324
+ if current_length > self.step_size:
325
+ break
326
+ t += dt
327
+
328
+ # --- START MODIFICATION (Fix for numerical stability) ---
329
+
330
+ # Calculate alphas and the IRC step
331
+ # Original: alphas = (np.exp(-eigenvalues*t) - 1) / eigenvalues
332
+ # This suffers from catastrophic cancellation (桁落ち) if (eigenvalues*t) is near zero.
333
+
334
+ x = -eigenvalues * t
335
+
336
+ # Use np.expm1(x) for numerical stability.
337
+ # np.expm1(x) calculates exp(x) - 1 accurately.
338
+ # We need (exp(x) - 1) / eigenvalues.
339
+ # Since x = -eigenvalues * t, then eigenvalues = -x / t
340
+ # (exp(x) - 1) / (-x / t) = -t * (exp(x) - 1) / x
341
+
342
+ # We use np.where to handle the limit x -> 0, where (exp(x)-1)/x -> 1, so alpha -> -t
343
+ small_x_mask = np.abs(x) < 1e-8
344
+
345
+ alphas = np.where(
346
+ small_x_mask,
347
+ -t, # Limit of (exp(x)-1)/eigenvalues as x->0 is -t
348
+ np.expm1(x) / eigenvalues # Use numerically stable function
349
+ )
350
+
351
+ # --- END MODIFICATION ---
352
+
353
+ A = np.dot(eigenvectors, np.dot(np.diag(alphas), eigenvectors.T))
354
+ step = np.dot(A, flattened_gradient)
355
+
356
+ # Reshape and un-mass-weight the step
357
+ step = step.reshape(len(geom_num_list), 3)
358
+ step = self.unmass_weight_step(step, sqrt_mass_list)
359
+
360
+ # Update geometry
361
+ new_geom = geom_num_list + step
362
+
363
+ # Remove center of mass motion
364
+ new_geom -= Calculationtools().calc_center_of_mass(new_geom, self.element_list)
365
+
366
+ return new_geom
367
+
368
+ def run(self):
369
+ """Run the LQA IRC calculation"""
370
+ print("Local Quadratic Approximation method")
371
+ geom_num_list = self.coords
372
+ CalcBiaspot = BiasPotentialCalculation(self.directory)
373
+
374
+ # Initialize oscillation counter
375
+ oscillation_counter = 0
376
+
377
+ for iter in range(1, self.max_step):
378
+ print("# STEP: ", iter)
379
+ exit_file_detect = os.path.exists(self.directory+"end.txt")
380
+
381
+ if exit_file_detect:
382
+ break
383
+
384
+ # Calculate energy, gradient and new geometry
385
+ e, g, geom_num_list, finish_frag = self.CE.single_point(
386
+ self.final_directory,
387
+ self.element_list,
388
+ iter,
389
+ self.electric_charge_and_multiplicity,
390
+ self.xtb_method,
391
+ UnitValueLib().bohr2angstroms * geom_num_list
392
+ )
393
+
394
+ # Calculate bias potential
395
+ _, B_e, B_g, BPA_hessian = CalcBiaspot.main(
396
+ e, g, geom_num_list, self.element_list,
397
+ self.force_data, g, iter-1, geom_num_list
398
+ )
399
+
400
+ if finish_frag:
401
+ break
402
+
403
+ # Recalculate Hessian if needed
404
+ if iter % self.FC_count == 0 and iter > 0:
405
+ self.mw_hessian = self.CE.Model_hess
406
+ self.mw_hessian = Calculationtools().project_out_hess_tr_and_rot(
407
+ self.mw_hessian, self.element_list, geom_num_list
408
+ )
409
+
410
+ # Get mass arrays for consistent mass-weighting
411
+ elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
412
+
413
+ # Mass-weight the hessian
414
+ mw_BPA_hessian = self.mass_weight_hessian(BPA_hessian, three_sqrt_mass_list)
415
+
416
+ # Mass-weight the coordinates
417
+ mw_geom_num_list = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
418
+
419
+ # Mass-weight the gradients
420
+ mw_g = self.mass_weight_gradient(g, sqrt_mass_list)
421
+ mw_B_g = self.mass_weight_gradient(B_g, sqrt_mass_list)
422
+
423
+ # Save structure to XYZ file
424
+ self.save_xyz_structure(iter, geom_num_list)
425
+
426
+ # Save energy and gradient data to CSV
427
+ self.save_to_csv(iter, e, B_e, g, B_g)
428
+
429
+ # Store IRC data for calculation purposes (limit to keep only necessary data)
430
+ # Keep only last 3 points for calculations like path bending angles and hessian updates
431
+ if len(self.irc_energy_list) >= 3:
432
+ self.irc_energy_list.pop(0)
433
+ self.irc_bias_energy_list.pop(0)
434
+ self.irc_mw_coords.pop(0)
435
+ self.irc_mw_gradients.pop(0)
436
+ self.irc_mw_bias_gradients.pop(0)
437
+
438
+ self.irc_energy_list.append(e)
439
+ self.irc_bias_energy_list.append(B_e)
440
+ self.irc_mw_coords.append(mw_geom_num_list)
441
+ self.irc_mw_gradients.append(mw_g)
442
+ self.irc_mw_bias_gradients.append(mw_B_g)
443
+
444
+ # Check for energy oscillations
445
+ if self.check_energy_oscillation(self.irc_bias_energy_list):
446
+ oscillation_counter += 1
447
+ print(f"Energy oscillation detected ({oscillation_counter}/5)")
448
+
449
+ if oscillation_counter >= 5:
450
+ print("Terminating IRC: Energy oscillated for 5 consecutive steps")
451
+ break
452
+ else:
453
+ # Reset counter if no oscillation is detected
454
+ oscillation_counter = 0
455
+
456
+ if iter > 1:
457
+ # Take LQA step
458
+ geom_num_list = self.step(
459
+ mw_B_g, geom_num_list, mw_BPA_hessian, sqrt_mass_list
460
+ )
461
+
462
+ # Calculate path bending angle
463
+ if iter > 2:
464
+ bend_angle = Calculationtools().calc_multi_dim_vec_angle(
465
+ self.irc_mw_coords[0]-self.irc_mw_coords[1],
466
+ self.irc_mw_coords[2]-self.irc_mw_coords[1]
467
+ )
468
+ self.path_bending_angle_list.append(np.degrees(bend_angle))
469
+ print("Path bending angle: ", np.degrees(bend_angle))
470
+
471
+ # Check for convergence
472
+ if convergence_check(B_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
473
+ print("Convergence reached. (IRC)")
474
+ break
475
+
476
+ else:
477
+ # First step is simple scaling along the gradient direction
478
+ normalized_grad = mw_B_g / np.linalg.norm(mw_B_g.flatten())
479
+ step = -normalized_grad * self.step_size * 0.05
480
+ step = self.unmass_weight_step(step, sqrt_mass_list)
481
+
482
+ geom_num_list = geom_num_list + step
483
+ geom_num_list -= Calculationtools().calc_center_of_mass(geom_num_list, self.element_list)
484
+
485
+ # Print current geometry
486
+ print()
487
+ for i in range(len(geom_num_list)):
488
+ x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
489
+ y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
490
+ z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
491
+ print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
492
+
493
+ # Display information
494
+ print()
495
+ print("Energy : ", e)
496
+ print("Bias Energy : ", B_e)
497
+ print("RMS B. grad : ", np.sqrt((B_g**2).mean()))
498
+
499
+
500
+ if len(self.irc_mw_coords) > 1:
501
+ # Calculate curvature properties
502
+ unit_tangent_vector, curvature_vector, scalar_curvature, curvature_coupling = calc_irc_curvature_properties(
503
+ mw_B_g, self.irc_mw_gradients[-2], self.mw_hessian, self.step_size
504
+ )
505
+
506
+ print("Scalar curvature: ", scalar_curvature)
507
+ # Print curvature_coupling as 6 columns, 8 decimal places
508
+ flat_cc = curvature_coupling.ravel()
509
+ print("Curvature coupling:")
510
+ for i in range(0, len(flat_cc), 6):
511
+ print(" ".join(f"{x: .8f}" for x in flat_cc[i:i+6]))
512
+
513
+ # Save curvature properties to file
514
+ save_curvature_properties_to_file(
515
+ os.path.join(self.directory, "irc_curvature_properties.csv"),
516
+ scalar_curvature,
517
+ curvature_coupling
518
+ )
519
+ print()
520
+
521
+ # Save final data visualization
522
+ G = Graph(self.directory)
523
+ rms_gradient_list = []
524
+ with open(self.csv_filename, 'r') as csvfile:
525
+ reader = csv.reader(csvfile)
526
+ next(reader) # Skip header
527
+ for row in reader:
528
+ rms_gradient_list.append(float(row[3]))
529
+
530
+ G.single_plot(
531
+ np.array(range(len(self.path_bending_angle_list))),
532
+ np.array(self.path_bending_angle_list),
533
+ self.directory,
534
+ atom_num=0,
535
+ axis_name_1="# STEP",
536
+ axis_name_2="bending angle [degrees]",
537
+ name="IRC_bending"
538
+ )
539
+
540
+ return