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/dvv.py ADDED
@@ -0,0 +1,544 @@
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.Visualization.visualization import Graph
11
+ from multioptpy.IRC.converge_criteria import convergence_check
12
+
13
+
14
+ class DVV:
15
+ """Damped Velocity Verlet method for IRC calculations
16
+
17
+ This method uses a damped classical trajectory algorithm with dynamic time step control.
18
+
19
+ References
20
+ ----------
21
+ [1] J. Phys. Chem. A, 106, 11, 2657-2667 (2002)
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, v0=0.04, dt0=0.5, error_tol=0.003, **kwargs):
27
+ """Initialize DVV 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
+ Target velocity magnitude (v0)
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
+ v0 : float, optional
56
+ Target velocity magnitude (in atomic units), default=0.04
57
+ dt0 : float, optional
58
+ Initial time step (in fs), default=0.5
59
+ error_tol : float, optional
60
+ Error tolerance for time step adjustments, default=0.003
61
+ """
62
+ self.max_step = max_step
63
+ self.step_size = step_size # Not directly used in DVV, but kept for consistency
64
+ self.ModelHessianUpdate = ModelHessianUpdate()
65
+ self.CE = calc_engine
66
+ self.FC_count = FC_count
67
+
68
+ # DVV specific parameters
69
+ self.v0 = v0
70
+ self.dt0 = dt0
71
+ self.error_tol = error_tol
72
+
73
+ # Initial condition
74
+ self.coords = init_coord
75
+ self.init_hess = init_hess
76
+ self.mw_hessian = init_hess # Mass-weighted hessian
77
+ self.xtb_method = xtb_method
78
+
79
+ # Convergence criteria
80
+ self.MAX_FORCE_THRESHOLD = 0.0004
81
+ self.RMS_FORCE_THRESHOLD = 0.0001
82
+
83
+ self.element_list = element_list
84
+ self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
85
+ self.directory = file_directory
86
+ self.final_directory = final_directory
87
+ self.force_data = force_data
88
+
89
+ # DVV trajectory data
90
+ self.velocities = [] # Store velocities
91
+ self.accelerations = [] # Store accelerations
92
+ self.time_steps = [] # Store time steps
93
+
94
+ # IRC data storage
95
+ self.irc_bias_energy_list = []
96
+ self.irc_energy_list = []
97
+ self.irc_mw_coords = []
98
+ self.irc_mw_gradients = []
99
+ self.irc_mw_bias_gradients = []
100
+ self.path_bending_angle_list = []
101
+
102
+ # Constants for unit conversion
103
+ self.BOHR2M = 5.29177210903e-11 # Bohr to meters
104
+ self.AU2J = 4.359744650e-18 # Hartree to Joules
105
+ self.AMU2KG = 1.660539040e-27 # AMU to kg
106
+
107
+ # Create data files
108
+ self.create_csv_file()
109
+ self.create_xyz_file()
110
+
111
+ def create_csv_file(self):
112
+ """Create CSV file for energy and gradient data"""
113
+ self.csv_filename = os.path.join(self.directory, "irc_energies_gradients.csv")
114
+ with open(self.csv_filename, 'w', newline='') as csvfile:
115
+ writer = csv.writer(csvfile)
116
+ writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)',
117
+ 'RMS Gradient', 'RMS Bias Gradient', 'Time Step (fs)', 'Damping Factor'])
118
+
119
+ def create_xyz_file(self):
120
+ """Create XYZ file for structure data"""
121
+ self.xyz_filename = os.path.join(self.directory, "irc_structures.xyz")
122
+ # Create empty file (will be appended to later)
123
+ open(self.xyz_filename, 'w').close()
124
+
125
+ def save_to_csv(self, step, energy, bias_energy, gradient, bias_gradient, time_step=None, damping=None):
126
+ """Save energy and gradient data to CSV file"""
127
+ rms_grad = np.sqrt((gradient**2).mean())
128
+ rms_bias_grad = np.sqrt((bias_gradient**2).mean())
129
+
130
+ with open(self.csv_filename, 'a', newline='') as csvfile:
131
+ writer = csv.writer(csvfile)
132
+ writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad,
133
+ time_step if time_step is not None else '',
134
+ damping if damping is not None else ''])
135
+
136
+ def save_xyz_structure(self, step, coords):
137
+ """Save molecular structure to XYZ file"""
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
+ mass_mat = np.diag(1.0 / three_sqrt_mass_list)
164
+ return np.dot(mass_mat, np.dot(hessian, mass_mat))
165
+
166
+ def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
167
+ """Convert coordinates to mass-weighted coordinates"""
168
+ mw_coords = copy.deepcopy(coordinates)
169
+ for i in range(len(coordinates)):
170
+ mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
171
+ return mw_coords
172
+
173
+ def mass_weight_gradient(self, gradient, sqrt_mass_list):
174
+ """Convert gradient to mass-weighted form"""
175
+ mw_gradient = copy.deepcopy(gradient)
176
+ for i in range(len(gradient)):
177
+ mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
178
+ return mw_gradient
179
+
180
+ def unmass_weight_step(self, step, sqrt_mass_list):
181
+ """Convert a step vector from mass-weighted to non-mass-weighted coordinates"""
182
+ unmw_step = copy.deepcopy(step)
183
+ for i in range(len(step)):
184
+ unmw_step[i] = step[i] / sqrt_mass_list[i]
185
+ return unmw_step
186
+
187
+ def mw_grad_to_acc(self, mw_gradient):
188
+ """Convert mass-weighted gradient to acceleration with proper units
189
+
190
+ Converts units of mass-weighted gradient [Hartree/(Bohr*sqrt(amu))]
191
+ to units of acceleration [sqrt(amu)*Bohr/fs²]
192
+
193
+ Parameters
194
+ ----------
195
+ mw_gradient : numpy.ndarray
196
+ Mass-weighted gradient
197
+
198
+ Returns
199
+ -------
200
+ numpy.ndarray
201
+ Acceleration with proper units
202
+ """
203
+ # The 1e30 comes from converting second² to femto second²
204
+ return mw_gradient * self.AU2J / self.AMU2KG / self.BOHR2M**2 / 1e30
205
+
206
+ def damp_velocity(self, velocity):
207
+ """Apply damping to velocity to maintain consistent step size
208
+
209
+ Parameters
210
+ ----------
211
+ velocity : numpy.ndarray
212
+ Velocity vector
213
+
214
+ Returns
215
+ -------
216
+ tuple
217
+ (damped_velocity, damping_factor)
218
+ """
219
+ # Compute damping factor to maintain consistent magnitude
220
+ v_norm = np.linalg.norm(velocity)
221
+ if v_norm < 1e-10:
222
+ damping_factor = 1.0
223
+ else:
224
+ damping_factor = self.v0 / v_norm
225
+
226
+ print(f"Damping factor={damping_factor:.6f}")
227
+ damped_velocity = velocity * damping_factor
228
+ return damped_velocity, damping_factor
229
+
230
+ def estimate_error(self, new_mw_coords, sqrt_mass_list):
231
+ """Estimate error for time step adjustment
232
+
233
+ Parameters
234
+ ----------
235
+ new_mw_coords : numpy.ndarray
236
+ New mass-weighted coordinates
237
+ sqrt_mass_list : numpy.ndarray
238
+ Array of sqrt(mass) values for each atom
239
+
240
+ Returns
241
+ -------
242
+ float
243
+ Estimated error for time step adjustment
244
+ """
245
+ print("Error estimation")
246
+
247
+ if len(self.irc_mw_coords) < 2 or len(self.time_steps) < 2 or len(self.velocities) < 2:
248
+ # Not enough history for error estimation
249
+ return self.error_tol
250
+
251
+ # Get current and previous time steps
252
+ cur_time_step = self.time_steps[-1]
253
+ prev_time_step = self.time_steps[-2]
254
+ time_step_sum = prev_time_step + cur_time_step
255
+ print(f"\tSum of current and previous timestep: {time_step_sum:.6f} fs")
256
+
257
+ # Calculate reference coordinates for error estimation (x' in the paper)
258
+ ref_coords = (
259
+ self.irc_mw_coords[-2] # x_i-2 in the paper
260
+ + np.dot(self.velocities[-2].reshape(-1), time_step_sum).reshape(new_mw_coords.shape) # v_i-2 * dt_sum
261
+ + 0.5 * np.dot(self.accelerations[-2].reshape(-1), time_step_sum**2).reshape(new_mw_coords.shape) # 0.5 * a_i-2 * dt_sum^2
262
+ )
263
+
264
+ # Calculate difference and scale by mass
265
+ diff = new_mw_coords - ref_coords
266
+ for i in range(len(diff)):
267
+ diff[i] = diff[i] / sqrt_mass_list[i]
268
+
269
+ # Calculate error metrics
270
+ largest_component = np.max(np.abs(diff))
271
+ norm = np.linalg.norm(diff)
272
+
273
+ print(f"\tmax(|diff|)={largest_component:.6f}")
274
+ print(f"\tnorm(diff)={norm:.6f}")
275
+
276
+ # Use the larger of the two metrics as the estimated error
277
+ estimated_error = max(largest_component, norm)
278
+ print(f"\testimated error={estimated_error:.6f}")
279
+
280
+ return estimated_error
281
+
282
+ def check_energy_oscillation(self, energy_list):
283
+ """Check if energy is oscillating (going up and down)"""
284
+ if len(energy_list) < 3:
285
+ return False
286
+
287
+ # Check if the energy changes direction (from increasing to decreasing or vice versa)
288
+ last_diff = energy_list[-1] - energy_list[-2]
289
+ prev_diff = energy_list[-2] - energy_list[-3]
290
+
291
+ # Return True if the energy direction has changed
292
+ return (last_diff * prev_diff) < 0
293
+
294
+ def step(self, mw_gradient, geom_num_list, mw_BPA_hessian, sqrt_mass_list):
295
+ """Calculate a single DVV IRC step
296
+
297
+ Parameters
298
+ ----------
299
+ mw_gradient : numpy.ndarray
300
+ Mass-weighted gradient
301
+ geom_num_list : numpy.ndarray
302
+ Current geometry coordinates
303
+ mw_BPA_hessian : numpy.ndarray
304
+ Mass-weighted bias potential hessian (not used in DVV)
305
+ sqrt_mass_list : numpy.ndarray
306
+ Array of sqrt(mass) values for each atom
307
+
308
+ Returns
309
+ -------
310
+ numpy.ndarray
311
+ New geometry coordinates
312
+ """
313
+ # First-time setup if needed
314
+ if not self.velocities:
315
+ # Initialize with current time step
316
+ self.time_steps.append(self.dt0)
317
+
318
+ # Convert gradient to acceleration (negative because gradient points uphill)
319
+ acceleration = -self.mw_grad_to_acc(mw_gradient)
320
+
321
+ # Initialize velocity with damped acceleration
322
+ initial_velocity, _ = self.damp_velocity(acceleration)
323
+
324
+ self.velocities.append(initial_velocity)
325
+ self.accelerations.append(acceleration)
326
+
327
+ # Convert mw_coords to mass weighted
328
+ mw_coords = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
329
+
330
+ # For first step, don't update coords yet
331
+ return geom_num_list
332
+
333
+ # Get previous values
334
+ prev_time_step = self.time_steps[-1]
335
+ prev_acceleration = self.accelerations[-1]
336
+ prev_velocity = self.velocities[-1]
337
+
338
+ # Convert mw_coords to mass weighted
339
+ mw_coords = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
340
+
341
+ # Calculate new acceleration from current gradient
342
+ acceleration = -self.mw_grad_to_acc(mw_gradient)
343
+
344
+ # Check for velocity-acceleration alignment (for logging)
345
+ acc_normed = acceleration / np.linalg.norm(acceleration)
346
+ prev_vel_normed = prev_velocity / np.linalg.norm(prev_velocity)
347
+ ovlp = np.sum(acc_normed * prev_vel_normed)
348
+ print(f"a @ v_i-1={ovlp:.8f}")
349
+
350
+ # Store acceleration
351
+ self.accelerations.append(acceleration)
352
+
353
+ # Update coordinates using Velocity Verlet algorithm (Eq. 2 in the paper)
354
+ new_mw_coords = (mw_coords
355
+ + prev_velocity * prev_time_step
356
+ + 0.5 * prev_acceleration * prev_time_step**2)
357
+
358
+ # Update velocity (Eq. 3 in the paper)
359
+ velocity = prev_velocity + 0.5 * (prev_acceleration + acceleration) * prev_time_step
360
+
361
+ # Damp velocity to maintain consistent step size
362
+ damped_velocity, damping_factor = self.damp_velocity(velocity)
363
+ self.velocities.append(damped_velocity)
364
+
365
+ # Estimate error for time step adjustment
366
+ estimated_error = self.estimate_error(new_mw_coords, sqrt_mass_list)
367
+
368
+ # Adjust time step based on error (Eq. 6 in the paper)
369
+ new_time_step = prev_time_step * (self.error_tol / estimated_error)**(1/3)
370
+
371
+ # Constrain time step between 0.0025 fs and 3.0 fs
372
+ new_time_step = min(new_time_step, 3.0)
373
+ new_time_step = max(new_time_step, 0.025)
374
+
375
+ print(f"\tCurrent time step={prev_time_step:.6f} fs")
376
+ print(f"\tNext time step={new_time_step:.6f} fs")
377
+
378
+ # Store the new time step
379
+ self.time_steps.append(new_time_step)
380
+
381
+ # Convert back to non-mass-weighted
382
+ new_geom = self.unmass_weight_step(new_mw_coords, sqrt_mass_list)
383
+
384
+ # Remove center of mass motion
385
+ new_geom -= Calculationtools().calc_center_of_mass(new_geom, self.element_list)
386
+
387
+ return new_geom
388
+
389
+ def run(self):
390
+ """Run the DVV IRC calculation"""
391
+ print("Damped Velocity Verlet method")
392
+ geom_num_list = self.coords
393
+ CalcBiaspot = BiasPotentialCalculation(self.directory)
394
+
395
+ # Initialize oscillation counter
396
+ oscillation_counter = 0
397
+
398
+ for iter in range(1, self.max_step):
399
+ print("# STEP: ", iter)
400
+ exit_file_detect = os.path.exists(self.directory+"end.txt")
401
+
402
+ if exit_file_detect:
403
+ break
404
+
405
+ # Calculate energy, gradient and new geometry
406
+ e, g, geom_num_list, finish_frag = self.CE.single_point(
407
+ self.final_directory,
408
+ self.element_list,
409
+ iter,
410
+ self.electric_charge_and_multiplicity,
411
+ self.xtb_method,
412
+ UnitValueLib().bohr2angstroms * geom_num_list
413
+ )
414
+
415
+ # Calculate bias potential
416
+ _, B_e, B_g, BPA_hessian = CalcBiaspot.main(
417
+ e, g, geom_num_list, self.element_list,
418
+ self.force_data, g, iter-1, geom_num_list
419
+ )
420
+
421
+ if finish_frag:
422
+ break
423
+
424
+ # Recalculate Hessian if needed
425
+ if iter % self.FC_count == 0:
426
+ self.mw_hessian = self.CE.Model_hess
427
+ self.mw_hessian = Calculationtools().project_out_hess_tr_and_rot(
428
+ self.mw_hessian, self.element_list, geom_num_list
429
+ )
430
+
431
+ # Get mass arrays for consistent mass-weighting
432
+ elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
433
+
434
+ # Mass-weight the hessian
435
+ mw_BPA_hessian = self.mass_weight_hessian(BPA_hessian, three_sqrt_mass_list)
436
+
437
+ # Mass-weight the coordinates
438
+ mw_geom_num_list = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
439
+
440
+ # Mass-weight the gradients
441
+ mw_g = self.mass_weight_gradient(g, sqrt_mass_list)
442
+ mw_B_g = self.mass_weight_gradient(B_g, sqrt_mass_list)
443
+
444
+ # Save structure to XYZ file
445
+ self.save_xyz_structure(iter, geom_num_list)
446
+
447
+ # Save energy and gradient data to CSV
448
+ current_dt = self.time_steps[-1] if self.time_steps else self.dt0
449
+ current_damping = damping_factor if 'damping_factor' in locals() else None
450
+ self.save_to_csv(iter, e, B_e, g, B_g, current_dt, current_damping)
451
+
452
+ # Store IRC data for calculation purposes (limit to keep only necessary data)
453
+ # Keep only last 3 points for calculations like path bending angles and hessian updates
454
+ if len(self.irc_energy_list) >= 3:
455
+ self.irc_energy_list.pop(0)
456
+ self.irc_bias_energy_list.pop(0)
457
+ self.irc_mw_coords.pop(0)
458
+ self.irc_mw_gradients.pop(0)
459
+ self.irc_mw_bias_gradients.pop(0)
460
+
461
+ self.irc_energy_list.append(e)
462
+ self.irc_bias_energy_list.append(B_e)
463
+ self.irc_mw_coords.append(mw_geom_num_list)
464
+ self.irc_mw_gradients.append(mw_g)
465
+ self.irc_mw_bias_gradients.append(mw_B_g)
466
+
467
+ # Check for energy oscillations
468
+ if self.check_energy_oscillation(self.irc_bias_energy_list):
469
+ oscillation_counter += 1
470
+ print(f"Energy oscillation detected ({oscillation_counter}/5)")
471
+
472
+ if oscillation_counter >= 5:
473
+ print("Terminating IRC: Energy oscillated for 5 consecutive steps")
474
+ break
475
+ else:
476
+ # Reset counter if no oscillation is detected
477
+ oscillation_counter = 0
478
+
479
+ # Take DVV step
480
+ geom_num_list = self.step(mw_B_g, geom_num_list, mw_BPA_hessian, sqrt_mass_list)
481
+
482
+ # Calculate path bending angle if we have enough points
483
+ if iter > 2 and len(self.irc_mw_coords) >= 3:
484
+ bend_angle = Calculationtools().calc_multi_dim_vec_angle(
485
+ self.irc_mw_coords[0]-self.irc_mw_coords[1],
486
+ self.irc_mw_coords[2]-self.irc_mw_coords[1]
487
+ )
488
+ self.path_bending_angle_list.append(np.degrees(bend_angle))
489
+ print("Path bending angle: ", np.degrees(bend_angle))
490
+
491
+ # Check for convergence
492
+ if convergence_check(B_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
493
+ print("Convergence reached. (IRC)")
494
+ break
495
+
496
+ # Print current geometry
497
+ print()
498
+ for i in range(len(geom_num_list)):
499
+ x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
500
+ y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
501
+ z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
502
+ print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
503
+
504
+ # Display information
505
+ print()
506
+ print("Energy : ", e)
507
+ print("Bias Energy : ", B_e)
508
+ print("RMS B. grad : ", np.sqrt((B_g**2).mean()))
509
+ if self.time_steps:
510
+ print(f"Time step : {self.time_steps[-1]:.6f} fs")
511
+ print()
512
+
513
+ # Save final data visualization
514
+ G = Graph(self.directory)
515
+ rms_gradient_list = []
516
+ with open(self.csv_filename, 'r') as csvfile:
517
+ reader = csv.reader(csvfile)
518
+ next(reader) # Skip header
519
+ for row in reader:
520
+ rms_gradient_list.append(float(row[3]))
521
+
522
+ G.single_plot(
523
+ np.array(range(len(self.path_bending_angle_list))),
524
+ np.array(self.path_bending_angle_list),
525
+ self.directory,
526
+ atom_num=0,
527
+ axis_name_1="# STEP",
528
+ axis_name_2="bending angle [degrees]",
529
+ name="IRC_bending"
530
+ )
531
+
532
+ # Also plot the time step evolution
533
+ G.single_plot(
534
+ np.array(range(len(self.time_steps))),
535
+ np.array(self.time_steps),
536
+ self.directory,
537
+ atom_num=0,
538
+ axis_name_1="# STEP",
539
+ axis_name_2="Time step (fs)",
540
+ name="DVV_timesteps"
541
+ )
542
+
543
+ return
544
+