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,629 @@
1
+ import numpy as np
2
+ import os
3
+
4
+ from multioptpy.Potential.potential import BiasPotentialCalculation
5
+ from multioptpy.Utils.calc_tools import Calculationtools
6
+ from multioptpy.Visualization.visualization import Graph
7
+ from multioptpy.Coordinate.polar_coordinate import cart2polar, polar2cart, cart_grad_2_polar_grad
8
+
9
+
10
+ class ElasticImagePair:
11
+ """
12
+ Implementation of the Improved Elastic Image Pair (iEIP) method
13
+ for finding minimum energy paths and transition states.
14
+ """
15
+ def __init__(self, config):
16
+ self.config = config
17
+
18
+ def RMS(self, mat):
19
+ """Calculate root mean square of a matrix"""
20
+ rms = np.sqrt(np.sum(mat**2))
21
+ return rms
22
+
23
+ def print_info(self, dat):
24
+ """Print optimization information"""
25
+ print("[[opt information]]")
26
+ print(" image_1 image_2")
27
+ print("energy (normal) : "+str(dat["energy_1"])+" "+str(dat["energy_2"]))
28
+ print("energy (bias) : "+str(dat["bias_energy_1"])+" "+str(dat["bias_energy_2"]))
29
+ print("gradient (normal, RMS) : "+str(self.RMS(dat["gradient_1"]))+" "+str(self.RMS(dat["gradient_2"])))
30
+ print("gradient (bias, RMS) : "+str(self.RMS(dat["bias_gradient_1"]))+" "+str(self.RMS(dat["bias_gradient_2"])))
31
+ print("perpendicular_force (RMS) : "+str(self.RMS(dat["perp_force_1"]))+" "+str(self.RMS(dat["perp_force_2"])))
32
+ print("energy_difference_dependent_force (RMS): "+str(self.RMS(dat["delta_energy_force_1"]))+" "+str(self.RMS(dat["delta_energy_force_2"])))
33
+ print("distance_dependent_force (RMS) : "+str(self.RMS(dat["close_target_force"])))
34
+ print("Total_displacement (RMS) : "+str(self.RMS(dat["total_disp_1"]))+" "+str(self.RMS(dat["total_disp_2"])))
35
+ print("Image_distance : "+str(dat["delta_geometry"]))
36
+
37
+ print("[[threshold]]")
38
+ print("Image_distance : ", self.config.img_distance_convage_criterion)
39
+
40
+ return
41
+
42
+ def lbfgs_update(self, s_list, y_list, grad, m=10):
43
+ """
44
+ L-BFGS algorithm to compute the search direction.
45
+
46
+ Parameters:
47
+ -----------
48
+ s_list : list of arrays
49
+ List of position differences (x_{k+1} - x_k)
50
+ y_list : list of arrays
51
+ List of gradient differences (g_{k+1} - g_k)
52
+ grad : array
53
+ Current gradient
54
+ m : int
55
+ Number of corrections to store
56
+
57
+ Returns:
58
+ --------
59
+ array
60
+ Search direction
61
+ """
62
+ k = len(s_list)
63
+ if k == 0:
64
+ return -grad
65
+
66
+ q = grad.copy()
67
+ alphas = np.zeros(k)
68
+ rhos = np.zeros(k)
69
+
70
+ # Compute rho values
71
+ for i in range(k):
72
+ rhos[i] = 1.0 / (np.dot(y_list[i], s_list[i]) + 1e-10)
73
+
74
+ # First loop (backward)
75
+ for i in range(k-1, -1, -1):
76
+ alphas[i] = rhos[i] * np.dot(s_list[i], q)
77
+ q = q - alphas[i] * y_list[i]
78
+
79
+ # Scaling factor
80
+ gamma = np.dot(s_list[-1], y_list[-1]) / (np.dot(y_list[-1], y_list[-1]) + 1e-10)
81
+ r = gamma * q
82
+
83
+ # Second loop (forward)
84
+ for i in range(k):
85
+ beta = rhos[i] * np.dot(y_list[i], r)
86
+ r = r + s_list[i] * (alphas[i] - beta)
87
+
88
+ return -r
89
+
90
+ def microiteration(self, SP1, SP2, FIO1, FIO2, file_directory_1, file_directory_2, element_list, init_electric_charge_and_multiplicity, final_electric_charge_and_multiplicity, prev_geom_num_list_1, prev_geom_num_list_2, iter):
91
+ """
92
+ Perform microiterations to optimize geometries with adaptive trust radius
93
+ and polar coordinate representation.
94
+ """
95
+ # Initialize L-BFGS storage
96
+ s_list_1, y_list_1 = [], []
97
+ s_list_2, y_list_2 = [], []
98
+ prev_grad_1, prev_grad_2 = None, None
99
+ prev_pos_1, prev_pos_2 = None, None
100
+ prev_energy_1, prev_energy_2 = None, None
101
+ max_lbfgs_memory = 10 # Store up to 10 previous steps
102
+
103
+ # Initialize trust region parameters with polar coordinate adaptations
104
+ # Base trust radius settings
105
+ trust_radius_1 = 0.015 # Initial trust radius (Bohr)
106
+ trust_radius_2 = 0.015
107
+ # Component weights for polar coordinates
108
+ radial_weight = 1.0 # Weight for radial components
109
+ angular_weight = 0.85 # Weight for angular components (smaller due to different scale)
110
+ # Trust radius limits
111
+ min_trust_radius = 0.01 # Minimum trust radius
112
+ max_trust_radius = 0.1 # Maximum trust radius
113
+ # Adjustment factors
114
+ good_step_factor = 1.25 # Increase factor for good steps
115
+ bad_step_factor = 0.5 # Decrease factor for bad steps
116
+ # Performance thresholds
117
+ excellent_ratio_threshold = 0.85 # Threshold for exceptionally good steps
118
+ very_bad_ratio_threshold = 0.2 # Threshold for very poor steps
119
+
120
+ for i in range(self.config.microiter_num):
121
+
122
+ energy_1, gradient_1, geom_num_list_1, error_flag_1 = SP1.single_point(file_directory_1, element_list, iter, init_electric_charge_and_multiplicity, self.config.force_data["xtb"])
123
+ energy_2, gradient_2, geom_num_list_2, error_flag_2 = SP2.single_point(file_directory_2, element_list, iter, final_electric_charge_and_multiplicity, self.config.force_data["xtb"])
124
+
125
+ BPC_1 = BiasPotentialCalculation(self.config.iEIP_FOLDER_DIRECTORY)
126
+ BPC_2 = BiasPotentialCalculation(self.config.iEIP_FOLDER_DIRECTORY)
127
+
128
+ _, bias_energy_1, bias_gradient_1, _ = BPC_1.main(energy_1, gradient_1, geom_num_list_1, element_list, self.config.force_data)
129
+ _, bias_energy_2, bias_gradient_2, _ = BPC_2.main(energy_2, gradient_2, geom_num_list_2, element_list, self.config.force_data)
130
+
131
+ if error_flag_1 or error_flag_2:
132
+ print("Error in QM calculation.")
133
+ with open(self.config.iEIP_FOLDER_DIRECTORY+"end.txt", "w") as f:
134
+ f.write("Error in QM calculation.")
135
+ break
136
+
137
+ microiter_force_1 = -cart_grad_2_polar_grad(geom_num_list_1.reshape(-1), bias_gradient_1.reshape(-1))
138
+ microiter_force_2 = -cart_grad_2_polar_grad(geom_num_list_2.reshape(-1), bias_gradient_2.reshape(-1))
139
+ microiter_force_1[0] = 0.0
140
+ microiter_force_2[0] = 0.0
141
+
142
+ # Update L-BFGS memory
143
+ if prev_grad_1 is not None and prev_pos_1 is not None:
144
+ s_1 = geom_num_list_1.reshape(-1) - prev_pos_1
145
+ y_1 = microiter_force_1 - prev_grad_1
146
+
147
+ # Only add if step and gradient difference are significant and curvature is positive
148
+ if np.linalg.norm(s_1) > 1e-10 and np.linalg.norm(y_1) > 1e-10 and np.dot(s_1, y_1) > 1e-10:
149
+ s_list_1.append(s_1)
150
+ y_list_1.append(y_1)
151
+ if len(s_list_1) > max_lbfgs_memory:
152
+ s_list_1.pop(0)
153
+ y_list_1.pop(0)
154
+
155
+ if prev_grad_2 is not None and prev_pos_2 is not None:
156
+ s_2 = geom_num_list_2.reshape(-1) - prev_pos_2
157
+ y_2 = microiter_force_2 - prev_grad_2
158
+
159
+ if np.linalg.norm(s_2) > 1e-10 and np.linalg.norm(y_2) > 1e-10 and np.dot(s_2, y_2) > 1e-10:
160
+ s_list_2.append(s_2)
161
+ y_list_2.append(y_2)
162
+ if len(s_list_2) > max_lbfgs_memory:
163
+ s_list_2.pop(0)
164
+ y_list_2.pop(0)
165
+
166
+ # Store current values for next iteration
167
+ prev_grad_1 = microiter_force_1.copy()
168
+ prev_grad_2 = microiter_force_2.copy()
169
+ prev_pos_1 = geom_num_list_1.reshape(-1).copy()
170
+ prev_pos_2 = geom_num_list_2.reshape(-1).copy()
171
+
172
+ if i % 10 == 0:
173
+ print("# Microiteration "+str(i))
174
+ print("Energy 1 :", energy_1)
175
+ print("Energy 2 :", energy_2)
176
+ print("Energy 1 (bias) :", bias_energy_1)
177
+ print("Energy 2 (bias) :", bias_energy_2)
178
+ print("RMS perpendicular force 1:", self.RMS(microiter_force_1[1:]))
179
+ print("RMS perpendicular force 2:", self.RMS(microiter_force_2[1:]))
180
+ print("Trust radius 1 :", trust_radius_1)
181
+ print("Trust radius 2 :", trust_radius_2)
182
+
183
+ polar_coord_1 = cart2polar(geom_num_list_1.reshape(-1), prev_geom_num_list_1.reshape(-1))
184
+ polar_coord_2 = cart2polar(geom_num_list_2.reshape(-1), prev_geom_num_list_2.reshape(-1))
185
+
186
+ # Apply L-BFGS to get initial step directions
187
+ if len(s_list_1) > 0:
188
+ total_disp_1 = self.lbfgs_update(s_list_1, y_list_1, -microiter_force_1)
189
+ else:
190
+ total_disp_1 = microiter_force_1.reshape(-1)
191
+ total_disp_1[0] = 0.0 # Keep the first component fixed
192
+ if len(s_list_2) > 0:
193
+ total_disp_2 = self.lbfgs_update(s_list_2, y_list_2, -microiter_force_2)
194
+ else:
195
+ total_disp_2 = microiter_force_2.reshape(-1)
196
+ total_disp_2[0] = 0.0 # Keep the first component fixed
197
+ # Create component-wise weight masks for polar coordinates
198
+ # Polar coordinate structure: [r, theta, phi, r, theta, phi, ...]
199
+ weights_1 = np.ones_like(total_disp_1)
200
+ weights_2 = np.ones_like(total_disp_2)
201
+
202
+ # Set different weights for radial and angular components
203
+ for j in range(0, len(weights_1), 3):
204
+ weights_1[j] = radial_weight # r component
205
+ weights_1[j+1:j+3] = angular_weight # theta, phi components
206
+
207
+ for j in range(0, len(weights_2), 3):
208
+ weights_2[j] = radial_weight # r component
209
+ weights_2[j+1:j+3] = angular_weight # theta, phi components
210
+
211
+ # Calculate weighted displacement norms for trust radius scaling
212
+ weighted_disp_1 = total_disp_1 * weights_1
213
+ weighted_disp_2 = total_disp_2 * weights_2
214
+
215
+ weighted_norm_1 = np.linalg.norm(weighted_disp_1)
216
+ weighted_norm_2 = np.linalg.norm(weighted_disp_2)
217
+
218
+ # Apply weighted trust region constraints
219
+ if weighted_norm_1 > trust_radius_1:
220
+ scale_factor_1 = trust_radius_1 / weighted_norm_1
221
+ total_disp_1 = total_disp_1 * scale_factor_1
222
+
223
+ if weighted_norm_2 > trust_radius_2:
224
+ scale_factor_2 = trust_radius_2 / weighted_norm_2
225
+ total_disp_2 = total_disp_2 * scale_factor_2
226
+
227
+ # Handle angular periodicities in polar coordinates
228
+ for j in range(1, len(total_disp_1), 3): # theta components
229
+ # Constrain theta (0 to π)
230
+ if polar_coord_1[j] + total_disp_1[j] > np.pi:
231
+ total_disp_1[j] = np.pi - polar_coord_1[j] - 1e-6
232
+ elif polar_coord_1[j] + total_disp_1[j] < 0:
233
+ total_disp_1[j] = -polar_coord_1[j] + 1e-6
234
+
235
+ # Constrain phi (0 to 2Ï€) - ensure shortest path
236
+ if j+1 < len(total_disp_1):
237
+ curr_phi = polar_coord_1[j+1]
238
+ target_phi = curr_phi + total_disp_1[j+1]
239
+
240
+ # Normalize phi to [0, 2Ï€]
241
+ while target_phi > 2*np.pi:
242
+ target_phi -= 2*np.pi
243
+ while target_phi < 0:
244
+ target_phi += 2*np.pi
245
+
246
+ # Find shortest angular path
247
+ phi_diff = target_phi - curr_phi
248
+ if abs(phi_diff) > np.pi:
249
+ if phi_diff > 0:
250
+ phi_diff -= 2*np.pi
251
+ else:
252
+ phi_diff += 2*np.pi
253
+ total_disp_1[j+1] = phi_diff
254
+
255
+ # Apply same periodic boundary handling for image 2
256
+ for j in range(1, len(total_disp_2), 3):
257
+ if polar_coord_2[j] + total_disp_2[j] > np.pi:
258
+ total_disp_2[j] = np.pi - polar_coord_2[j] - 1e-6
259
+ elif polar_coord_2[j] + total_disp_2[j] < 0:
260
+ total_disp_2[j] = -polar_coord_2[j] + 1e-6
261
+
262
+ if j+1 < len(total_disp_2):
263
+ curr_phi = polar_coord_2[j+1]
264
+ target_phi = curr_phi + total_disp_2[j+1]
265
+
266
+ while target_phi > 2*np.pi:
267
+ target_phi -= 2*np.pi
268
+ while target_phi < 0:
269
+ target_phi += 2*np.pi
270
+
271
+ phi_diff = target_phi - curr_phi
272
+ if abs(phi_diff) > np.pi:
273
+ if phi_diff > 0:
274
+ phi_diff -= 2*np.pi
275
+ else:
276
+ phi_diff += 2*np.pi
277
+ total_disp_2[j+1] = phi_diff
278
+
279
+ # Calculate predicted energy reduction
280
+ pred_reduction_1 = np.dot(microiter_force_1, total_disp_1)
281
+ pred_reduction_2 = np.dot(microiter_force_2, total_disp_2)
282
+
283
+ # Apply step
284
+ new_polar_coord_1 = polar_coord_1 + total_disp_1
285
+ new_polar_coord_2 = polar_coord_2 + total_disp_2
286
+
287
+ tmp_geom_1 = polar2cart(new_polar_coord_1, prev_geom_num_list_1.reshape(-1))
288
+ tmp_geom_2 = polar2cart(new_polar_coord_2, prev_geom_num_list_2.reshape(-1))
289
+
290
+ geom_num_list_1 = tmp_geom_1.reshape(len(geom_num_list_1), 3)
291
+ geom_num_list_2 = tmp_geom_2.reshape(len(geom_num_list_2), 3)
292
+
293
+ # Create input files with new geometries
294
+ new_geom_num_list_1_tolist = (geom_num_list_1*self.config.bohr2angstroms).tolist()
295
+ new_geom_num_list_2_tolist = (geom_num_list_2*self.config.bohr2angstroms).tolist()
296
+ for j, elem in enumerate(element_list):
297
+ new_geom_num_list_1_tolist[j].insert(0, elem)
298
+ new_geom_num_list_2_tolist[j].insert(0, elem)
299
+
300
+ new_geom_num_list_1_tolist.insert(0, init_electric_charge_and_multiplicity)
301
+ new_geom_num_list_2_tolist.insert(0, final_electric_charge_and_multiplicity)
302
+
303
+ file_directory_1 = FIO1.make_psi4_input_file([new_geom_num_list_1_tolist], iter)
304
+ file_directory_2 = FIO2.make_psi4_input_file([new_geom_num_list_2_tolist], iter)
305
+
306
+ # Update trust radius based on actual vs. predicted reduction
307
+ if prev_energy_1 is not None and prev_energy_2 is not None:
308
+ actual_reduction_1 = prev_energy_1 - bias_energy_1
309
+ actual_reduction_2 = prev_energy_2 - bias_energy_2
310
+
311
+ # Calculate performance ratio
312
+ ratio_1 = actual_reduction_1 / (pred_reduction_1 + 1e-10)
313
+ ratio_2 = actual_reduction_2 / (pred_reduction_2 + 1e-10)
314
+
315
+ # Update trust radius based on performance metrics
316
+ if ratio_1 < very_bad_ratio_threshold: # Very poor step
317
+ trust_radius_1 = max(trust_radius_1 * bad_step_factor, min_trust_radius)
318
+ elif ratio_1 > excellent_ratio_threshold and weighted_norm_1 >= 0.9 * trust_radius_1: # Excellent step
319
+ trust_radius_1 = min(trust_radius_1 * good_step_factor, max_trust_radius)
320
+
321
+ if ratio_2 < very_bad_ratio_threshold: # Very poor step
322
+ trust_radius_2 = max(trust_radius_2 * bad_step_factor, min_trust_radius)
323
+ elif ratio_2 > excellent_ratio_threshold and weighted_norm_2 >= 0.9 * trust_radius_2: # Excellent step
324
+ trust_radius_2 = min(trust_radius_2 * good_step_factor, max_trust_radius)
325
+
326
+ # Auto-adjust weights if large angular displacements are occurring
327
+ max_angle_disp_1 = max([abs(total_disp_1[j]) for j in range(1, len(total_disp_1), 3) if j < len(total_disp_1)])
328
+ max_angle_disp_2 = max([abs(total_disp_2[j]) for j in range(1, len(total_disp_2), 3) if j < len(total_disp_2)])
329
+
330
+ if max_angle_disp_1 > 0.3: # Threshold for excessive angular displacement
331
+ angular_weight = max(angular_weight * 0.9, 0.1) # Decrease angular weight
332
+
333
+ if max_angle_disp_2 > 0.3:
334
+ angular_weight = max(angular_weight * 0.9, 0.1)
335
+
336
+ # Store current energies for next iteration
337
+ prev_energy_1 = bias_energy_1
338
+ prev_energy_2 = bias_energy_2
339
+
340
+ # Convergence check
341
+ if self.RMS(microiter_force_1) < 0.01 and self.RMS(microiter_force_2) < 0.01:
342
+ print("Optimization converged.")
343
+ break
344
+
345
+ return energy_1, gradient_1, bias_energy_1, bias_gradient_1, geom_num_list_1, energy_2, gradient_2, bias_energy_2, bias_gradient_2, geom_num_list_2
346
+
347
+ def iteration(self, file_directory_1, file_directory_2, SP1, SP2, element_list, init_electric_charge_and_multiplicity, final_electric_charge_and_multiplicity, FIO1, FIO2):
348
+ """
349
+ Main elastic image pair optimization iteration.
350
+ """
351
+ G = Graph(self.config.iEIP_FOLDER_DIRECTORY)
352
+ beta_m = 0.9
353
+ beta_v = 0.999
354
+ BIAS_GRAD_LIST_A = []
355
+ BIAS_GRAD_LIST_B = []
356
+ BIAS_ENERGY_LIST_A = []
357
+ BIAS_ENERGY_LIST_B = []
358
+
359
+ GRAD_LIST_A = []
360
+ GRAD_LIST_B = []
361
+ ENERGY_LIST_A = []
362
+ ENERGY_LIST_B = []
363
+ prev_delta_geometry = 0.0
364
+
365
+ for iter in range(0, self.config.microiterlimit):
366
+ if os.path.isfile(self.config.iEIP_FOLDER_DIRECTORY+"end.txt"):
367
+ break
368
+ print("# ITR. "+str(iter))
369
+
370
+ energy_1, gradient_1, geom_num_list_1, error_flag_1 = SP1.single_point(file_directory_1, element_list, iter, init_electric_charge_and_multiplicity, self.config.force_data["xtb"])
371
+ energy_2, gradient_2, geom_num_list_2, error_flag_2 = SP2.single_point(file_directory_2, element_list, iter, final_electric_charge_and_multiplicity, self.config.force_data["xtb"])
372
+ geom_num_list_1, geom_num_list_2 = Calculationtools().kabsch_algorithm(geom_num_list_1, geom_num_list_2)
373
+
374
+ if error_flag_1 or error_flag_2:
375
+ print("Error in QM calculation.")
376
+ with open(self.config.iEIP_FOLDER_DIRECTORY+"end.txt", "w") as f:
377
+ f.write("Error in QM calculation.")
378
+ break
379
+
380
+ if iter == 0:
381
+ m_1 = gradient_1 * 0.0
382
+ m_2 = gradient_1 * 0.0
383
+ v_1 = gradient_1 * 0.0
384
+ v_2 = gradient_1 * 0.0
385
+ ini_geom_1 = geom_num_list_1
386
+ ini_geom_2 = geom_num_list_2
387
+
388
+ BPC_1 = BiasPotentialCalculation(self.config.iEIP_FOLDER_DIRECTORY)
389
+ BPC_2 = BiasPotentialCalculation(self.config.iEIP_FOLDER_DIRECTORY)
390
+
391
+ _, bias_energy_1, bias_gradient_1, _ = BPC_1.main(energy_1, gradient_1, geom_num_list_1, element_list, self.config.force_data)
392
+ _, bias_energy_2, bias_gradient_2, _ = BPC_2.main(energy_2, gradient_2, geom_num_list_2, element_list, self.config.force_data)
393
+
394
+ if self.config.microiter_num > 0 and iter > 0:
395
+ energy_1, gradient_1, bias_energy_1, bias_gradient_1, geom_num_list_1, energy_2, gradient_2, bias_energy_2, bias_gradient_2, geom_num_list_2 = self.microiteration(SP1, SP2, FIO1, FIO2, file_directory_1, file_directory_2, element_list, init_electric_charge_and_multiplicity, final_electric_charge_and_multiplicity, prev_geom_num_list_1, prev_geom_num_list_2, iter)
396
+ if os.path.isfile(self.config.iEIP_FOLDER_DIRECTORY+"end.txt"):
397
+ break
398
+
399
+ # Determine which image is higher in energy for proper force direction
400
+ if energy_2 > energy_1:
401
+ N = self.norm_dist_2imgs(geom_num_list_1, geom_num_list_2)
402
+ L = self.dist_2imgs(geom_num_list_1, geom_num_list_2)
403
+ else:
404
+ N = self.norm_dist_2imgs(geom_num_list_2, geom_num_list_1)
405
+ L = self.dist_2imgs(geom_num_list_2, geom_num_list_1)
406
+
407
+ Lt = self.target_dist_2imgs(L)
408
+
409
+ force_disp_1 = self.displacement(bias_gradient_1)
410
+ force_disp_2 = self.displacement(bias_gradient_2)
411
+
412
+ perp_force_1 = self.perpendicular_force(bias_gradient_1, N)
413
+ perp_force_2 = self.perpendicular_force(bias_gradient_2, N)
414
+
415
+ delta_energy_force_1 = self.delta_energy_force(bias_energy_1, bias_energy_2, N, L)
416
+ delta_energy_force_2 = self.delta_energy_force(bias_energy_1, bias_energy_2, N, L)
417
+
418
+ close_target_force = self.close_target_force(L, Lt, geom_num_list_1, geom_num_list_2)
419
+
420
+ perp_disp_1 = self.displacement(perp_force_1)
421
+ perp_disp_2 = self.displacement(perp_force_2)
422
+
423
+ delta_energy_disp_1 = self.displacement(delta_energy_force_1)
424
+ delta_energy_disp_2 = self.displacement(delta_energy_force_2)
425
+
426
+ close_target_disp = self.displacement(close_target_force)
427
+
428
+ if iter == 0:
429
+ ini_force_1 = perp_force_1 * 0.0
430
+ ini_force_2 = perp_force_2 * 0.0
431
+ ini_disp_1 = ini_force_1
432
+ ini_disp_2 = ini_force_2
433
+ close_target_disp_1 = close_target_disp
434
+ close_target_disp_2 = close_target_disp
435
+ else:
436
+ ini_force_1 = self.initial_structure_dependent_force(geom_num_list_1, ini_geom_1)
437
+ ini_force_2 = self.initial_structure_dependent_force(geom_num_list_2, ini_geom_2)
438
+ ini_disp_1 = self.displacement_prime(ini_force_1)
439
+ ini_disp_2 = self.displacement_prime(ini_force_2)
440
+
441
+ # Based on DS-AFIR method
442
+ Z_1 = np.linalg.norm(geom_num_list_1 - ini_geom_1) / np.linalg.norm(geom_num_list_1 - geom_num_list_2) + (np.sum((geom_num_list_1 - ini_geom_1) * (geom_num_list_1 - geom_num_list_2))) / (np.linalg.norm(geom_num_list_1 - ini_geom_1) * np.linalg.norm(geom_num_list_1 - geom_num_list_2))
443
+ Z_2 = np.linalg.norm(geom_num_list_2 - ini_geom_2) / np.linalg.norm(geom_num_list_2 - geom_num_list_1) + (np.sum((geom_num_list_2 - ini_geom_2) * (geom_num_list_2 - geom_num_list_1))) / (np.linalg.norm(geom_num_list_2 - ini_geom_2) * np.linalg.norm(geom_num_list_2 - geom_num_list_1))
444
+
445
+ if Z_1 > 0.0:
446
+ Y_1 = Z_1 / (Z_1 + 1) + 0.5
447
+ else:
448
+ Y_1 = 0.5
449
+
450
+ if Z_2 > 0.0:
451
+ Y_2 = Z_2 / (Z_2 + 1) + 0.5
452
+ else:
453
+ Y_2 = 0.5
454
+
455
+ u_1 = Y_1 * ((geom_num_list_1 - geom_num_list_2) / np.linalg.norm(geom_num_list_1 - geom_num_list_2)) - (1.0 - Y_1) * ((geom_num_list_1 - ini_geom_1) / np.linalg.norm(geom_num_list_1 - ini_geom_1))
456
+ u_2 = Y_2 * ((geom_num_list_2 - geom_num_list_1) / np.linalg.norm(geom_num_list_2 - geom_num_list_1)) - (1.0 - Y_2) * ((geom_num_list_2 - ini_geom_2) / np.linalg.norm(geom_num_list_2 - ini_geom_2))
457
+
458
+ X_1 = self.config.BETA / np.linalg.norm(u_1) - (np.sum(gradient_1 * u_1) / np.linalg.norm(u_1) ** 2)
459
+ X_2 = self.config.BETA / np.linalg.norm(u_2) - (np.sum(gradient_2 * u_2) / np.linalg.norm(u_2) ** 2)
460
+
461
+ ini_disp_1 *= X_1 * (1.0 - Y_1)
462
+ ini_disp_2 *= X_2 * (1.0 - Y_2)
463
+
464
+ close_target_disp_1 = close_target_disp * X_1 * Y_1
465
+ close_target_disp_2 = close_target_disp * X_2 * Y_2
466
+
467
+ total_disp_1 = -perp_disp_1 + delta_energy_disp_1 + close_target_disp_1 - force_disp_1 + ini_disp_1
468
+ total_disp_2 = -perp_disp_2 - delta_energy_disp_2 - close_target_disp_2 - force_disp_2 + ini_disp_2
469
+
470
+ # AdaBelief optimizer: https://doi.org/10.48550/arXiv.2010.07468
471
+ m_1 = beta_m * m_1 + (1 - beta_m) * total_disp_1
472
+ m_2 = beta_m * m_2 + (1 - beta_m) * total_disp_2
473
+ v_1 = beta_v * v_1 + (1 - beta_v) * (total_disp_1 - m_1)**2
474
+ v_2 = beta_v * v_2 + (1 - beta_v) * (total_disp_2 - m_2)**2
475
+
476
+ adabelief_1 = 0.01 * (m_1 / (np.sqrt(v_1) + 1e-8))
477
+ adabelief_2 = 0.01 * (m_2 / (np.sqrt(v_2) + 1e-8))
478
+
479
+ new_geom_num_list_1 = geom_num_list_1 + adabelief_1
480
+ new_geom_num_list_2 = geom_num_list_2 + adabelief_2
481
+
482
+ new_geom_num_list_1, new_geom_num_list_2 = Calculationtools().kabsch_algorithm(new_geom_num_list_1, new_geom_num_list_2)
483
+
484
+ if iter != 0:
485
+ prev_delta_geometry = delta_geometry
486
+
487
+ delta_geometry = np.linalg.norm(new_geom_num_list_2 - new_geom_num_list_1)
488
+ rms_perp_force = np.linalg.norm(np.sqrt(perp_force_1 ** 2 + perp_force_2 ** 2))
489
+
490
+ info_dat = {
491
+ "perp_force_1": perp_force_1,
492
+ "perp_force_2": perp_force_2,
493
+ "delta_energy_force_1": delta_energy_force_1,
494
+ "delta_energy_force_2": delta_energy_force_2,
495
+ "close_target_force": close_target_force,
496
+ "perp_disp_1": perp_disp_1,
497
+ "perp_disp_2": perp_disp_2,
498
+ "delta_energy_disp_1": delta_energy_disp_1,
499
+ "delta_energy_disp_2": delta_energy_disp_2,
500
+ "close_target_disp": close_target_disp,
501
+ "total_disp_1": total_disp_1,
502
+ "total_disp_2": total_disp_2,
503
+ "bias_energy_1": bias_energy_1,
504
+ "bias_energy_2": bias_energy_2,
505
+ "bias_gradient_1": bias_gradient_1,
506
+ "bias_gradient_2": bias_gradient_2,
507
+ "energy_1": energy_1,
508
+ "energy_2": energy_2,
509
+ "gradient_1": gradient_1,
510
+ "gradient_2": gradient_2,
511
+ "delta_geometry": delta_geometry,
512
+ "rms_perp_force": rms_perp_force
513
+ }
514
+
515
+ self.print_info(info_dat)
516
+
517
+ # Prepare geometries for next iteration
518
+ new_geom_num_list_1_tolist = (new_geom_num_list_1*self.config.bohr2angstroms).tolist()
519
+ new_geom_num_list_2_tolist = (new_geom_num_list_2*self.config.bohr2angstroms).tolist()
520
+ for i, elem in enumerate(element_list):
521
+ new_geom_num_list_1_tolist[i].insert(0, elem)
522
+ new_geom_num_list_2_tolist[i].insert(0, elem)
523
+
524
+ new_geom_num_list_1_tolist.insert(0, init_electric_charge_and_multiplicity)
525
+ new_geom_num_list_2_tolist.insert(0, final_electric_charge_and_multiplicity)
526
+
527
+ file_directory_1 = FIO1.make_psi4_input_file([new_geom_num_list_1_tolist], iter+1)
528
+ file_directory_2 = FIO2.make_psi4_input_file([new_geom_num_list_2_tolist], iter+1)
529
+
530
+ # Record data for plotting
531
+ BIAS_ENERGY_LIST_A.append(bias_energy_1*self.config.hartree2kcalmol)
532
+ BIAS_ENERGY_LIST_B.append(bias_energy_2*self.config.hartree2kcalmol)
533
+ BIAS_GRAD_LIST_A.append(np.sqrt(np.sum(bias_gradient_1**2)))
534
+ BIAS_GRAD_LIST_B.append(np.sqrt(np.sum(bias_gradient_2**2)))
535
+
536
+ ENERGY_LIST_A.append(energy_1*self.config.hartree2kcalmol)
537
+ ENERGY_LIST_B.append(energy_2*self.config.hartree2kcalmol)
538
+ GRAD_LIST_A.append(np.sqrt(np.sum(gradient_1**2)))
539
+ GRAD_LIST_B.append(np.sqrt(np.sum(gradient_2**2)))
540
+
541
+ prev_geom_num_list_1 = geom_num_list_1
542
+ prev_geom_num_list_2 = geom_num_list_2
543
+
544
+ # Check convergence
545
+ if delta_geometry < self.config.img_distance_convage_criterion: # Bohr
546
+ print("Converged!!!")
547
+ break
548
+
549
+ # Adjust beta if images are diverging
550
+ if delta_geometry > prev_delta_geometry:
551
+ self.config.BETA *= 1.02
552
+ else:
553
+ print("Reached maximum number of iterations. This is not converged.")
554
+ with open(self.config.iEIP_FOLDER_DIRECTORY+"not_converged.txt", "w") as f:
555
+ f.write("Reached maximum number of iterations. This is not converged.")
556
+
557
+ # Create energy and gradient profile plots
558
+ bias_ene_list = BIAS_ENERGY_LIST_A + BIAS_ENERGY_LIST_B[::-1]
559
+ bias_grad_list = BIAS_GRAD_LIST_A + BIAS_GRAD_LIST_B[::-1]
560
+
561
+ ene_list = ENERGY_LIST_A + ENERGY_LIST_B[::-1]
562
+ grad_list = GRAD_LIST_A + GRAD_LIST_B[::-1]
563
+ NUM_LIST = [i for i in range(len(ene_list))]
564
+
565
+ G.single_plot(NUM_LIST, ene_list, file_directory_1, "energy", axis_name_2="energy [kcal/mol]", name="energy")
566
+ G.single_plot(NUM_LIST, grad_list, file_directory_1, "gradient", axis_name_2="grad (RMS) [a.u.]", name="gradient")
567
+ G.single_plot(NUM_LIST, bias_ene_list, file_directory_1, "bias_energy", axis_name_2="energy [kcal/mol]", name="energy")
568
+ G.single_plot(NUM_LIST, bias_grad_list, file_directory_1, "bias_gradient", axis_name_2="grad (RMS) [a.u.]", name="gradient")
569
+ FIO1.make_traj_file_for_DM(img_1="A", img_2="B")
570
+
571
+ # Identify critical points
572
+ FIO1.argrelextrema_txt_save(ene_list, "approx_TS", "max")
573
+ FIO1.argrelextrema_txt_save(ene_list, "approx_EQ", "min")
574
+ FIO1.argrelextrema_txt_save(grad_list, "local_min_grad", "min")
575
+
576
+ return
577
+
578
+ def norm_dist_2imgs(self, geom_num_list_1, geom_num_list_2):
579
+ """Calculate normalized distance vector between two images"""
580
+ L = self.dist_2imgs(geom_num_list_1, geom_num_list_2)
581
+ N = (geom_num_list_2 - geom_num_list_1) / L
582
+ return N
583
+
584
+ def dist_2imgs(self, geom_num_list_1, geom_num_list_2):
585
+ """Calculate distance between two images"""
586
+ L = np.linalg.norm(geom_num_list_2 - geom_num_list_1) + 1e-10
587
+ return L # Bohr
588
+
589
+ def target_dist_2imgs(self, L):
590
+ """Calculate target distance between two images"""
591
+ Lt = max(L * 0.9, self.config.L_covergence - 0.01)
592
+ return Lt
593
+
594
+ def force_R(self, L):
595
+ """Calculate force magnitude based on distance"""
596
+ F_R = min(max(L/self.config.L_covergence, 1)) * self.F_R_convage_criterion
597
+ return F_R
598
+
599
+ def displacement(self, force):
600
+ """Calculate displacement from force with magnitude limit"""
601
+ n_force = np.linalg.norm(force)
602
+ displacement = (force / (n_force + 1e-10)) * min(n_force, self.config.displacement_limit)
603
+ return displacement
604
+
605
+ def displacement_prime(self, force):
606
+ """Calculate displacement from force with fixed magnitude"""
607
+ n_force = np.linalg.norm(force)
608
+ displacement = (force / (n_force + 1e-10)) * self.config.displacement_limit
609
+ return displacement
610
+
611
+ def initial_structure_dependent_force(self, geom, ini_geom):
612
+ """Calculate force toward initial structure"""
613
+ ini_force = geom - ini_geom
614
+ return ini_force
615
+
616
+ def perpendicular_force(self, gradient, N):
617
+ """Calculate force component perpendicular to path"""
618
+ perp_force = gradient.reshape(len(gradient)*3, 1) - np.dot(gradient.reshape(1, len(gradient)*3), N.reshape(len(gradient)*3, 1)) * N.reshape(len(gradient)*3, 1)
619
+ return perp_force.reshape(len(gradient), 3) # (atomnum×3, ndarray)
620
+
621
+ def delta_energy_force(self, ene_1, ene_2, N, L):
622
+ """Calculate force component due to energy difference"""
623
+ d_ene_force = N * abs(ene_1 - ene_2) / L
624
+ return d_ene_force
625
+
626
+ def close_target_force(self, L, Lt, geom_num_list_1, geom_num_list_2):
627
+ """Calculate force component to maintain target distance"""
628
+ ct_force = (geom_num_list_2 - geom_num_list_1) * (L - Lt) / L
629
+ return ct_force