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,273 @@
1
+ import numpy as np
2
+ from scipy.optimize import newton
3
+
4
+ from multioptpy.Utils.calc_tools import Calculationtools
5
+
6
+ class TRIM:
7
+ def __init__(self, saddle_order=0):
8
+ """
9
+ Trust Region Image Minimization (TRIM) for transition state optimization.
10
+
11
+ ref.: https://doi.org/10.1016/0009-2614(91)90115-P
12
+ Helgaker, 1991
13
+ """
14
+ # Trust region parameters
15
+ self.trust_radius = 0.3 # Default trust radius
16
+ self.trust_radius_min = 0.01 # Minimum trust radius
17
+ self.trust_radius_max = 0.5 # Maximum trust radius
18
+
19
+ # Transition state mode parameters
20
+ self.roots = [0] # Indices of eigenvalues to follow uphill (default: lowest mode)
21
+ self.ts_mode_history = [] # History of transition state modes
22
+ self.mode_following_threshold = 0.7 # Dot product threshold for mode following
23
+
24
+ # Step control parameters
25
+ self.max_step_norm = 0.5 # Maximum allowed step size
26
+ self.min_eigenvalue_shift = 1e-8 # Minimum eigenvalue shift for numerical stability
27
+
28
+ # Convergence tracking
29
+ self.predicted_energy_changes = []
30
+ self.iter = 0
31
+ self.H = None # Store Hessian for energy prediction
32
+ self.saddle_order = saddle_order # Desired saddle order
33
+ # Logging and diagnostics
34
+ self.debug = False
35
+
36
+ def log(self, message):
37
+ """Print log message if debug is enabled."""
38
+ if self.debug:
39
+ print(f"TRIM: {message}")
40
+ else:
41
+ # Always print important information even when debug is disabled
42
+ if "μ=" in message or "norm(step)" in message:
43
+ print(f"TRIM: {message}")
44
+
45
+ def update_ts_mode(self, eigvals, eigvecs):
46
+ """
47
+ Update the transition state mode to follow based on eigenvalues and eigenvectors.
48
+
49
+ Parameters:
50
+ -----------
51
+ eigvals : numpy.ndarray
52
+ Eigenvalues of the Hessian matrix
53
+ eigvecs : numpy.ndarray
54
+ Eigenvectors of the Hessian matrix
55
+ """
56
+ # Sort eigenvalues to find the lowest (potentially negative) ones
57
+ idx = np.argsort(eigvals)
58
+
59
+ # Default to following the lowest mode (idx[0])
60
+ if not self.ts_mode_history:
61
+ self.roots = [idx[0:self.saddle_order]]
62
+ self.ts_mode_history.append(eigvecs[:, idx[0:self.saddle_order]])
63
+ return
64
+
65
+ # If we have history, ensure we follow the consistent mode
66
+ prev_mode = self.ts_mode_history[-1]
67
+ overlaps = np.abs([np.dot(prev_mode.flatten(), eigvecs[:, i].flatten()) for i in idx[:3]])
68
+
69
+ # Find the mode with highest overlap with previous mode
70
+ max_overlap_idx = np.argmax(overlaps)
71
+ if overlaps[max_overlap_idx] > self.mode_following_threshold:
72
+ # Use the mode with highest overlap
73
+ self.roots = [idx[max_overlap_idx]]
74
+ else:
75
+ # Default to lowest eigenvalue if no good overlap
76
+ self.roots = [idx[0:self.saddle_order]]
77
+
78
+ self.ts_mode_history.append(eigvecs[:, self.roots[0:self.saddle_order]])
79
+ self.log(f"Following mode with eigenvalue {eigvals[self.roots[0]]:.6f}")
80
+
81
+ def quadratic_model(self, gradient, hessian, step):
82
+ """
83
+ Predict energy change using quadratic model.
84
+
85
+ Parameters:
86
+ -----------
87
+ gradient : numpy.ndarray
88
+ Current gradient
89
+ hessian : numpy.ndarray
90
+ Current Hessian matrix
91
+ step : numpy.ndarray
92
+ Step vector
93
+
94
+ Returns:
95
+ --------
96
+ float
97
+ Predicted energy change
98
+ """
99
+ step_flat = step.flatten()
100
+ grad_flat = gradient.flatten()
101
+
102
+ linear_term = np.dot(grad_flat, step_flat)
103
+ quadratic_term = 0.5 * np.dot(step_flat, np.dot(hessian, step_flat))
104
+
105
+ return linear_term + quadratic_term
106
+
107
+ def get_step(self, forces, hessian, eigvals, eigvecs):
108
+ """
109
+ Calculate the TRIM step.
110
+
111
+ Parameters:
112
+ -----------
113
+
114
+ forces : numpy.ndarray
115
+ Current forces (-gradient)
116
+ hessian : numpy.ndarray
117
+ Current Hessian matrix
118
+ eigvals : numpy.ndarray
119
+ Eigenvalues of the Hessian
120
+ eigvecs : numpy.ndarray
121
+ Eigenvectors of the Hessian
122
+
123
+ Returns:
124
+ --------
125
+ numpy.ndarray
126
+ Calculated step vector
127
+ """
128
+ gradient = -forces
129
+ self.H = hessian # Store for energy prediction
130
+
131
+
132
+ # Update transition state mode
133
+ if self.saddle_order > 0:
134
+ self.update_ts_mode(eigvals, eigvecs)
135
+ print(f"Signs of eigenvalue and -vector of root(s) {self.roots} will be reversed!")
136
+
137
+
138
+ # Transform gradient to basis of eigenvectors
139
+ gradient_ = eigvecs.T.dot(gradient.flatten())
140
+
141
+ if self.saddle_order > 0:
142
+ # Construct image function by inverting the signs of the eigenvalue and
143
+ # -vector of the mode to follow uphill.
144
+ eigvals_ = eigvals.copy()
145
+ eigvals_[self.roots] *= -1
146
+ gradient_ = gradient_.copy()
147
+ gradient_[self.roots] *= -1
148
+ else:
149
+ eigvals_ = eigvals.copy()
150
+ gradient_ = gradient_.copy()
151
+
152
+ def get_step(mu):
153
+ """Calculate step with level shift parameter mu."""
154
+ zetas = -gradient_ / (eigvals_ - mu)
155
+ # Replace nan with 0.
156
+ zetas = np.nan_to_num(zetas)
157
+ # Transform to original basis
158
+ step = np.dot(eigvecs, zetas[:, np.newaxis])
159
+ return step
160
+
161
+ def get_step_norm(mu):
162
+ """Calculate norm of step with level shift parameter mu."""
163
+ return np.linalg.norm(get_step(mu))
164
+
165
+ def func(mu):
166
+ """Function to find mu such that step norm equals trust radius."""
167
+ return get_step_norm(mu) - self.trust_radius
168
+
169
+ # Initialize level shift parameter
170
+ mu = 0
171
+ norm0 = get_step_norm(mu)
172
+
173
+ # Apply trust radius constraint if needed
174
+ if norm0 > self.trust_radius:
175
+ try:
176
+ mu, res = newton(func, x0=mu, full_output=True)
177
+ if res.converged:
178
+ self.log(f"Using levelshift of μ={mu:.4f}")
179
+ else:
180
+ self.log("Newton method for levelshift did not converge, using simple scaling")
181
+ mu = 0
182
+ step = get_step(mu)
183
+ step = step * (self.trust_radius / norm0)
184
+ return step
185
+ except:
186
+ self.log("Error in levelshift calculation, using simple scaling")
187
+ mu = 0
188
+ step = get_step(mu)
189
+ step = step * (self.trust_radius / norm0)
190
+ return step
191
+ else:
192
+ self.log("Took pure newton step without levelshift")
193
+
194
+ # Calculate final step
195
+ step = get_step(mu)
196
+ step_norm = np.linalg.norm(step)
197
+ self.log(f"norm(step)={step_norm:.6f}")
198
+
199
+ # Store predicted energy change
200
+ predicted_change = self.quadratic_model(gradient, self.H, step)
201
+ self.predicted_energy_changes.append(predicted_change)
202
+
203
+ return step
204
+
205
+ def run(self, geom_num_list, B_g, hessian, trust_radius, original_move_vector):
206
+ """
207
+ Run TRIM optimization step
208
+
209
+ Parameters:
210
+ -----------
211
+ geom_num_list : numpy.ndarray
212
+ Current geometry
213
+ energy : float
214
+ Current energy value
215
+ B_g : numpy.ndarray
216
+ Current gradient (forces with sign flipped)
217
+ original_move_vector : numpy.ndarray
218
+ Step calculated by the original method (ignored in TRIM)
219
+ hessian : numpy.ndarray, optional
220
+ Current Hessian matrix (required for TRIM)
221
+ trust_radius : float
222
+ Trust radius for the optimization step
223
+ original_move_vector : numpy.ndarray
224
+ Step calculated by the original method (ignored in TRIM)
225
+
226
+ Returns:
227
+ --------
228
+ numpy.ndarray
229
+ Optimized step vector
230
+ """
231
+ print("TRIM method")
232
+ n_coords = len(geom_num_list)
233
+ step_norm = np.linalg.norm(original_move_vector)
234
+ print(f"Original step norm: {step_norm:.6f}, Trust radius: {trust_radius:.6f}")
235
+ self.trust_radius = trust_radius
236
+ if step_norm < self.trust_radius:
237
+ print("Step is within trust radius, using original move vector")
238
+ return original_move_vector
239
+
240
+ # Check if Hessian is provided
241
+ if hessian is None:
242
+ print("Error: Hessian matrix is required for TRIM method")
243
+ return original_move_vector # Fallback to steepest descent
244
+
245
+ hessian = Calculationtools().project_out_hess_tr_and_rot_for_coord(hessian, geom_num_list.reshape(-1, 3), geom_num_list.reshape(-1, 3), display_eigval=False)
246
+
247
+ # Compute eigenvalues and eigenvectors of the Hessian
248
+ try:
249
+ eigvals, eigvecs = np.linalg.eigh(hessian)
250
+ except np.linalg.LinAlgError:
251
+ print("Warning: Eigenvalue computation failed, using diagonal approximation")
252
+ # Use diagonal approximation if eigenvalue computation fails
253
+ H_diag = np.diag(np.diag(hessian))
254
+ eigvals = np.diag(H_diag)
255
+ eigvecs = np.eye(len(eigvals))
256
+
257
+ # Reshape forces for compatibility
258
+ forces = -B_g.reshape(-1) # Convert gradient to forces (sign flip)
259
+
260
+ # Calculate TRIM step
261
+ move_vector = self.get_step(forces, hessian, eigvals, eigvecs)
262
+
263
+ # Ensure step has correct shape
264
+ move_vector = -1*move_vector.reshape(n_coords, 1)
265
+
266
+ # Final safety checks
267
+ move_norm = np.linalg.norm(move_vector)
268
+ if move_norm < 1e-10 or np.any(np.isnan(move_vector)) or np.any(np.isinf(move_vector)):
269
+ print("Warning: Step issue detected, using scaled gradient instead")
270
+ move_vector = original_move_vector
271
+
272
+ self.iter += 1
273
+ return move_vector
@@ -0,0 +1,207 @@
1
+ import numpy as np
2
+
3
+ class TrustRadius:
4
+ def __init__(self,
5
+ initial_trust_radius=0.3,
6
+ min_trust_radius=0.01,
7
+ max_trust_radius=0.5,
8
+ history_size=5,
9
+ adaptive_factor_scale=0.8,
10
+ energy_precision_threshold=1e-8,
11
+ quality='Normal'):
12
+ """
13
+ Initialize the composite-step trust radius method
14
+
15
+ Args:
16
+ initial_trust_radius: Initial trust radius value
17
+ min_trust_radius: Minimum allowed trust radius
18
+ max_trust_radius: Maximum allowed trust radius
19
+ history_size: Number of previous steps to consider in history
20
+ adaptive_factor_scale: Scale factor for adaptive adjustment (0-1)
21
+ energy_precision_threshold: Threshold for energy precision
22
+ quality: Convergence quality setting ('VeryBasic', 'Basic', 'Normal', 'Good', 'VeryGood')
23
+ """
24
+ # Basic parameters
25
+ self.trust_radius = initial_trust_radius
26
+ self.min_trust_radius = min_trust_radius
27
+ self.max_trust_radius = max_trust_radius
28
+
29
+ # Adaptive parameters
30
+ self.history_size = history_size
31
+ self.adaptive_factor_scale = adaptive_factor_scale
32
+ self.energy_precision_threshold = energy_precision_threshold
33
+
34
+ # History data
35
+ self.energy_ratios = [] # History of actual/predicted energy change ratios
36
+ self.step_sizes = [] # History of step sizes
37
+ self.energy_changes = [] # History of energy changes
38
+
39
+ # Optimization state
40
+ self.iteration_count = 0
41
+ self.last_energy = None
42
+
43
+
44
+ # AMS convergence criteria based on quality setting
45
+ self.set_convergence_quality(quality)
46
+
47
+ def set_min_trust_radius(self, min_trust_radius):
48
+ """Set minimum trust radius"""
49
+ self.min_trust_radius = min_trust_radius
50
+
51
+ def set_max_trust_radius(self, max_trust_radius):
52
+ """Set maximum trust radius"""
53
+ self.max_trust_radius = max_trust_radius
54
+
55
+
56
+
57
+ def set_convergence_quality(self, quality):
58
+ """
59
+ Set convergence criteria based on quality setting
60
+
61
+ Args:
62
+ quality: Quality setting ('VeryBasic', 'Basic', 'Normal', 'Good', 'VeryGood')
63
+ """
64
+ # Default to Normal if invalid quality is provided
65
+ if quality not in ['VeryBasic', 'Basic', 'Normal', 'Good', 'VeryGood']:
66
+ quality = 'Normal'
67
+
68
+ quality_map = {
69
+ 'VeryBasic': {'energy': 1e-3, 'gradients': 1e-1, 'step': 1.0, 'stress': 5e-2},
70
+ 'Basic': {'energy': 1e-4, 'gradients': 1e-2, 'step': 0.1, 'stress': 5e-3},
71
+ 'Normal': {'energy': 1e-5, 'gradients': 1e-3, 'step': 0.01, 'stress': 5e-4},
72
+ 'Good': {'energy': 1e-6, 'gradients': 1e-4, 'step': 0.001, 'stress': 5e-5},
73
+ 'VeryGood': {'energy': 1e-7, 'gradients': 1e-5, 'step': 0.0001, 'stress': 5e-6}
74
+ }
75
+
76
+ self.convergence_criteria = quality_map[quality]
77
+ self.quality = quality
78
+
79
+ def calculate_adaptive_factor(self):
80
+ """
81
+ Calculate adaptive adjustment factor based on optimization history
82
+
83
+ Returns:
84
+ float: Adaptive factor determining adjustment strength
85
+ """
86
+ # Return default value if no history is available
87
+ if len(self.energy_ratios) == 0:
88
+ return 2.0
89
+
90
+ # Calculate based on recent prediction accuracy
91
+ recent_ratios = self.energy_ratios[-min(self.history_size, len(self.energy_ratios)):]
92
+ ratio_variance = np.var(recent_ratios) if len(recent_ratios) > 1 else 0.0
93
+
94
+ # More cautious adjustment when prediction variance is high
95
+ base_factor = 2.0 * np.exp(-ratio_variance)
96
+
97
+ # More cautious when approaching convergence
98
+ if self.is_approaching_convergence():
99
+ base_factor *= self.adaptive_factor_scale
100
+
101
+
102
+
103
+ return max(1.1, min(base_factor, 3.0)) # Limit to range 1.1-3.0
104
+
105
+ def is_approaching_convergence(self):
106
+ """
107
+ Determine if optimization is approaching convergence
108
+
109
+ Returns:
110
+ bool: True if approaching convergence
111
+ """
112
+ if len(self.energy_changes) < 2:
113
+ return False
114
+
115
+ # Check if recent energy changes are small and decreasing
116
+ recent_changes = np.abs(self.energy_changes[-min(3, len(self.energy_changes)):])
117
+ return np.all(recent_changes < 0.01) and np.mean(recent_changes) < 0.005
118
+
119
+
120
+ def update_trust_radii(self,
121
+ B_e,
122
+ pre_B_e,
123
+ pre_B_g,
124
+ pre_move_vector,
125
+ model_hess,
126
+ geom_num_list,
127
+ trust_radii,
128
+ atom_types=None,
129
+ constraints=None):
130
+ """
131
+ Update trust radius using composite-step approach
132
+
133
+ Args:
134
+ B_e: Current energy
135
+ pre_B_e: Previous energy
136
+ pre_B_g: Previous gradient
137
+ pre_move_vector: Previous movement vector
138
+ model_hess: Model Hessian
139
+ geom_num_list: Geometry number list
140
+ trust_radii: Current trust radius
141
+ atom_types: Optional list of atom types
142
+ constraints: Optional constraint information
143
+
144
+ Returns:
145
+ float: Updated trust radius
146
+ """
147
+ if self.iteration_count == 0:
148
+ self.iteration_count += 1
149
+ return trust_radii
150
+
151
+
152
+ # Calculate predicted energy change
153
+ Ce = (np.dot(pre_B_g.reshape(1, len(geom_num_list)),
154
+ pre_move_vector.reshape(len(geom_num_list), 1)) +
155
+ 0.5 * np.dot(np.dot(pre_move_vector.reshape(1, len(geom_num_list)),
156
+ model_hess),
157
+ pre_move_vector.reshape(len(geom_num_list), 1)))
158
+
159
+ # Handle numerical stability
160
+ if abs(Ce) < self.energy_precision_threshold:
161
+ Ce += np.sign(Ce) * self.energy_precision_threshold
162
+ if abs(Ce) < self.energy_precision_threshold: # If sign is unknown
163
+ Ce = self.energy_precision_threshold
164
+
165
+ # Ratio of actual to predicted energy change
166
+ r = (pre_B_e - B_e) / Ce
167
+
168
+ # Update history
169
+ self.energy_ratios.append(float(r))
170
+ self.step_sizes.append(float(np.linalg.norm(pre_move_vector)))
171
+ self.energy_changes.append(float(pre_B_e - B_e))
172
+
173
+ # Calculate adaptive factor
174
+ adaptive_factor = self.calculate_adaptive_factor()
175
+
176
+ # Debug information
177
+ print(f"Iteration: {self.iteration_count}")
178
+ print(f"Energy ratio (actual/predicted): {r}")
179
+ print(f"Adaptive factor: {adaptive_factor}")
180
+ print(f"Current trust radius: {trust_radii}")
181
+
182
+ # Trust radius adjustment logic (improved version)
183
+ r_min = 0.25
184
+ r_good = 0.75
185
+
186
+ if r <= r_min or r >= (2.0 - r_min):
187
+ # Decrease trust radius for poor prediction
188
+ trust_radii /= adaptive_factor
189
+ print("Decrease trust radius")
190
+ elif r >= r_good and r <= (2.0 - r_good):
191
+ # For good prediction
192
+ if abs(np.linalg.norm(pre_move_vector) - trust_radii) < self.energy_precision_threshold:
193
+ # Increase trust radius if previous step was limited by trust radius
194
+ trust_radii *= adaptive_factor ** 0.5
195
+ print("Increase trust radius")
196
+ else:
197
+ # Otherwise maintain trust radius
198
+ print("Keep trust radius (good prediction)")
199
+ else:
200
+ # Maintain trust radius for moderate prediction
201
+ print("Keep trust radius (moderate prediction)")
202
+
203
+ self.iteration_count += 1
204
+
205
+ # Clip final trust radius between min and max values
206
+ return np.clip(trust_radii, self.min_trust_radius, self.max_trust_radius)
207
+
@@ -0,0 +1,121 @@
1
+ import numpy as np
2
+
3
+
4
+ class TR_NEB:
5
+ def __init__(self, **config):
6
+ self.NEB_FOLDER_DIRECTORY = config.get("NEB_FOLDER_DIRECTORY", None)
7
+ self.fix_init_edge = config.get("fix_init_edge", False)
8
+ self.fix_end_edge = config.get("fix_end_edge", False)
9
+ self.apply_convergence_criteria = config.get("apply_convergence_criteria", False)
10
+ self.threshold_max_force = 0.00045
11
+ self.threshold_rms_force = 0.00030
12
+ self.threshold_max_displacement = 0.0018
13
+ self.threshold_rms_displacement = 0.0012
14
+
15
+ return
16
+
17
+ def TR_calc(self, geometry_num_list, total_force_list, total_delta, biased_energy_list, pre_biased_energy_list, pre_geom):
18
+ if self.fix_init_edge:
19
+ move_vector = [total_delta[0]*0.0]
20
+ else:
21
+ init_norm_move_vector = np.linalg.norm(total_delta[0])
22
+ init_tr = min(0.5, init_norm_move_vector)
23
+ if init_norm_move_vector < 1e-15:
24
+ move_vector = [total_delta[0]*0.0]
25
+ else:
26
+ move_vector = [init_tr * total_delta[0] / init_norm_move_vector]
27
+
28
+ trust_radii_1_list = []
29
+ trust_radii_2_list = []
30
+
31
+ for i in range(1, len(total_delta)-1):
32
+ #if biased_energy_list[i] > pre_biased_energy_list[i] and pre_geom is not None:
33
+ # total_delta[i] = pre_geom[i] - geometry_num_list[i]
34
+ # print("Energy increased... ")
35
+
36
+ trust_radii_1 = np.linalg.norm(geometry_num_list[i] - geometry_num_list[i-1]) / 2.0
37
+ trust_radii_2 = np.linalg.norm(geometry_num_list[i] - geometry_num_list[i+1]) / 2.0
38
+
39
+ trust_radii_1_list.append(str(trust_radii_1*2))
40
+ trust_radii_2_list.append(str(trust_radii_2*2))
41
+
42
+ normalized_vec_1 = (geometry_num_list[i-1] - geometry_num_list[i])/(np.linalg.norm(geometry_num_list[i-1] - geometry_num_list[i]) + 1e-15)
43
+ normalized_vec_2 = (geometry_num_list[i+1] - geometry_num_list[i])/(np.linalg.norm(geometry_num_list[i+1] - geometry_num_list[i]) + 1e-15)
44
+ normalized_delta = total_delta[i] / np.linalg.norm(total_delta[i])
45
+
46
+ cos_1 = np.sum(normalized_vec_1 * normalized_delta)
47
+ cos_2 = np.sum(normalized_vec_2 * normalized_delta)
48
+
49
+ force_move_vec_cos = np.sum(total_force_list[i] * total_delta[i]) / (np.linalg.norm(total_force_list[i]) * np.linalg.norm(total_delta[i]))
50
+
51
+ if force_move_vec_cos >= 0: #Projected velocity-verlet algorithm
52
+ if (cos_1 > 0 and cos_2 < 0) or (cos_1 < 0 and cos_2 > 0):
53
+ if np.linalg.norm(total_delta[i]) > trust_radii_1 and cos_1 > 0:
54
+ move_vector.append(total_delta[i]*trust_radii_1/np.linalg.norm(total_delta[i]))
55
+
56
+ elif np.linalg.norm(total_delta[i]) > trust_radii_2 and cos_2 > 0:
57
+ move_vector.append(total_delta[i]*trust_radii_2/np.linalg.norm(total_delta[i]))
58
+
59
+ else:
60
+ move_vector.append(total_delta[i])
61
+
62
+ elif (cos_1 < 0 and cos_2 < 0):
63
+ move_vector.append(total_delta[i])
64
+
65
+ else:
66
+ if np.linalg.norm(total_delta[i]) > trust_radii_1:
67
+ move_vector.append(total_delta[i]*trust_radii_1/np.linalg.norm(total_delta[i]))
68
+
69
+ elif np.linalg.norm(total_delta[i]) > trust_radii_2:
70
+ move_vector.append(total_delta[i]*trust_radii_2/np.linalg.norm(total_delta[i]))
71
+
72
+ else:
73
+ move_vector.append(total_delta[i])
74
+ else:
75
+ print("no displacements (Projected velocity-verlet algorithm): # NODE "+str(i))
76
+ move_vector.append(total_delta[i] * 0.0)
77
+
78
+
79
+ with open(self.NEB_FOLDER_DIRECTORY+"procrustes_distance_1.csv", "a") as f:
80
+ f.write(",".join(trust_radii_1_list)+"\n")
81
+
82
+ with open(self.NEB_FOLDER_DIRECTORY+"procrustes_distance_2.csv", "a") as f:
83
+ f.write(",".join(trust_radii_2_list)+"\n")
84
+
85
+ if self.fix_end_edge:
86
+ move_vector.append(total_delta[-1]*0.0)
87
+ else:
88
+ end_norm_move_vector = np.linalg.norm(total_delta[-1])
89
+ end_tr = min(0.5, end_norm_move_vector)
90
+ if end_norm_move_vector < 1e-15:
91
+ move_vector.append(total_delta[-1]*0.0)
92
+ else:
93
+ move_vector.append(end_tr * total_delta[-1] / end_norm_move_vector)
94
+
95
+ if self.apply_convergence_criteria:
96
+ move_vector = self.check_convergence(total_force_list, move_vector)
97
+
98
+ return move_vector
99
+
100
+ def check_convergence(self, total_force_list, move_vec_list):
101
+
102
+ for i in range(1, len(total_force_list)-1):
103
+ max_grad = np.max(total_force_list[i])
104
+ rms_grad = np.sqrt(np.sum(total_force_list[i]**2)/(len(total_force_list[i])*3))
105
+ max_move = np.max(move_vec_list[i])
106
+ rms_move = np.sqrt(np.sum(move_vec_list[i]**2)/(len(move_vec_list[i])*3))
107
+ print("--------------------")
108
+ print("NODE #"+str(i))
109
+ print(f"MAXIMUM NEB FORCE : {float(max_grad):12.8f}")
110
+ print(f"RMS NEB FORCE : {float(rms_grad):12.8f}")
111
+ print(f"MAXIMUM DISPLACEMENT : {float(max_move):12.8f}")
112
+ print(f"RMS DISPLACEMENT : {float(rms_move):12.8f}")
113
+
114
+ if max_grad < self.threshold_max_force and rms_grad < self.threshold_rms_force and max_move < self.threshold_max_displacement and rms_move < self.threshold_rms_displacement:
115
+ print("Converged?: YES")
116
+ move_vec_list[i] = move_vec_list[i]*0.0
117
+ else:
118
+ print("Converged?: NO")
119
+ print("--------------------")
120
+ return move_vec_list
121
+
@@ -0,0 +1,60 @@
1
+ import numpy as np
2
+ import copy
3
+
4
+
5
+ class YOGI:
6
+ def __init__(self, **config):
7
+ #https://doi.org/10.1145/3441501.3441507
8
+ self.adam_count = 1
9
+ self.DELTA = 0.05
10
+ self.beta_m = 0.9
11
+ self.beta_v = 0.999
12
+ self.Epsilon = 1e-8
13
+ self.Initialization = True
14
+ self.config = config
15
+ self.hessian = None
16
+ self.bias_hessian = None
17
+
18
+ return
19
+
20
+ def run(self, geom_num_list, B_g, pre_B_g=[], pre_geom=[], B_e=0.0, pre_B_e=0.0, pre_move_vector=[], initial_geom_num_list=[], g=[], pre_g=[]):
21
+ print("YOGI")
22
+ if self.Initialization:
23
+ self.adam_m = geom_num_list * 0.0
24
+ self.adam_v = geom_num_list * 0.0
25
+ self.Initialization = False
26
+
27
+ adam_count = self.adam_count
28
+ adam_m = self.adam_m
29
+ adam_v = self.adam_v
30
+ new_adam_m = adam_m*0.0
31
+ new_adam_v = adam_v*0.0
32
+
33
+ for i in range(len(geom_num_list)):
34
+ new_adam_m[i] = copy.copy(self.beta_m*adam_m[i] + (1.0-self.beta_m)*(B_g[i]))
35
+ new_adam_v[i] = copy.copy(adam_v[i] - (1.0 - self.beta_v) * np.sign(adam_v[i] - B_g[i] ** 2) * (B_g[i])**2)
36
+
37
+
38
+ move_vector = []
39
+
40
+ for i in range(len(geom_num_list)):
41
+ move_vector.append(self.DELTA*(new_adam_m[i])/np.sqrt((new_adam_v[i])+self.Epsilon))
42
+
43
+ self.adam_m = new_adam_m
44
+ self.adam_v = new_adam_v
45
+ self.adam_count += 1
46
+
47
+ return move_vector#Bohr
48
+ def set_hessian(self, hessian):
49
+ self.hessian = hessian
50
+ return
51
+
52
+ def set_bias_hessian(self, bias_hessian):
53
+ self.bias_hessian = bias_hessian
54
+ return
55
+
56
+ def get_hessian(self):
57
+ return self.hessian
58
+
59
+ def get_bias_hessian(self):
60
+ return self.bias_hessian
File without changes