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,245 @@
1
+ import numpy as np
2
+ from numpy.linalg import norm
3
+
4
+ from multioptpy.Parameters.parameter import UnitValueLib
5
+
6
+ class QuickMin_NEB:
7
+ """QuickMin optimizer for Nudged Elastic Band calculations.
8
+
9
+ QuickMin is a minimization algorithm that combines molecular dynamics with
10
+ steepest descent optimization. It retains the velocity component in the direction
11
+ of the force only when it's aligned with the force, providing efficient energy
12
+ minimization for complex systems.
13
+
14
+ Reference:
15
+ D. Sheppard, R. Terrell, and G. Henkelman, J. Chem. Phys. 128, 134106 (2008)
16
+ """
17
+
18
+ def __init__(self, **config):
19
+ # Configuration parameters
20
+ self.config = config
21
+
22
+ # Initialize flags
23
+ self.Initialization = True
24
+ self.iter = 0
25
+
26
+ # QuickMin parameters
27
+ self.dt = config.get("dt", 0.1) # Time step
28
+ self.dt_max = config.get("dt_max", 0.2) # Maximum time step
29
+ self.dt_min = config.get("dt_min", 0.01) # Minimum time step
30
+ self.dt_grow = config.get("dt_grow", 1.1) # Time step growth factor
31
+ self.dt_shrink = config.get("dt_shrink", 0.5) # Time step shrink factor
32
+ self.velocity_mixing = config.get("velocity_mixing", 0.9) # Velocity mixing factor
33
+
34
+ # Adaptive time steps for each image
35
+ self.dt_images = None
36
+
37
+ # Maximum step size constraint
38
+ self.maxstep = config.get("maxstep", 0.1)
39
+
40
+ # Storage for velocities and previous data
41
+ self.velocities = None
42
+ self.prev_forces = None
43
+ self.prev_positions = None
44
+ self.prev_energies = None
45
+
46
+ # Unit conversion
47
+ self.bohr2angstroms = UnitValueLib().bohr2angstroms
48
+ if 'bohr2angstroms' in config:
49
+ self.bohr2angstroms = config['bohr2angstroms']
50
+
51
+ # For compatibility with other NEB implementations
52
+ self.TR_NEB = config.get("TR_NEB", None)
53
+
54
+ print(f"Initialized QuickMin optimizer for NEB with dt={self.dt}, maxstep={self.maxstep}")
55
+ print(f"Using per-image adaptive time steps with growth factor={self.dt_grow}, shrink factor={self.dt_shrink}")
56
+
57
+ def initialize_data(self, num_images):
58
+ """Initialize data structures for the optimization.
59
+
60
+ Parameters:
61
+ ----------
62
+ num_images : int
63
+ Number of images in the NEB calculation
64
+ """
65
+ # Initialize time steps for each image
66
+ self.dt_images = np.ones(num_images) * self.dt
67
+
68
+ # Initialize velocities with zero (no initial momentum)
69
+ self.velocities = [np.zeros_like(img) for img in range(num_images)]
70
+
71
+ # Initialize storage for previous data
72
+ self.prev_forces = [None] * num_images
73
+ self.prev_positions = [None] * num_images
74
+ self.prev_energies = [None] * num_images
75
+
76
+ print(f"Initialized QuickMin data for {num_images} images")
77
+
78
+ def update_velocities_and_positions(self, positions, forces, energies):
79
+ """Update velocities and positions using the QuickMin algorithm.
80
+
81
+ Parameters:
82
+ ----------
83
+ positions : list of ndarray
84
+ Current positions for all images
85
+ forces : list of ndarray
86
+ Current forces for all images
87
+ energies : list of float
88
+ Current energies for all images
89
+
90
+ Returns:
91
+ -------
92
+ tuple
93
+ (new_velocities, steps) - Updated velocities and position steps for all images
94
+ """
95
+ new_velocities = []
96
+ steps = []
97
+
98
+ # Process each image independently
99
+ for i, (position, force) in enumerate(zip(positions, forces)):
100
+ # Initialize velocity if needed
101
+ if self.velocities[i] is None or not isinstance(self.velocities[i], np.ndarray):
102
+ self.velocities[i] = np.zeros_like(position)
103
+
104
+ # Ensure correct shape
105
+ if self.velocities[i].shape != force.shape:
106
+ self.velocities[i] = np.zeros_like(force)
107
+
108
+ velocity = self.velocities[i]
109
+
110
+ # Calculate dot product of velocity and force
111
+ v_dot_f = np.dot(velocity.flatten(), force.flatten())
112
+
113
+ # Reset velocity if moving against the force
114
+ if v_dot_f <= 0:
115
+ velocity = np.zeros_like(velocity)
116
+ v_dot_f = 0
117
+
118
+ # Calculate force magnitude
119
+ f_norm = norm(force.flatten())
120
+
121
+ # Project velocity onto force direction
122
+ if f_norm > 1e-10:
123
+ f_hat = force.flatten() / f_norm
124
+ v_parallel = v_dot_f * f_hat
125
+
126
+ # Update velocity (only keep component parallel to force)
127
+ new_v = v_parallel.reshape(velocity.shape)
128
+
129
+ # Add force contribution for this time step
130
+ new_v += self.dt_images[i] * force
131
+ else:
132
+ # No force, maintain zero velocity
133
+ new_v = np.zeros_like(velocity)
134
+
135
+ # Apply velocity mixing for stability
136
+ if self.velocity_mixing < 1.0:
137
+ new_v = self.velocity_mixing * new_v + (1.0 - self.velocity_mixing) * velocity
138
+
139
+ # Calculate step
140
+ step = self.dt_images[i] * new_v
141
+
142
+ # Apply maxstep constraint
143
+ step_length = norm(step.flatten())
144
+ if step_length > self.maxstep:
145
+ step = step * (self.maxstep / step_length)
146
+
147
+ # Adjust time step based on energy change if we have previous data
148
+ if self.prev_energies[i] is not None:
149
+ energy_change = self.prev_energies[i] - energies[i]
150
+ if energy_change > 0: # Energy decreased (good)
151
+ # Increase time step
152
+ self.dt_images[i] = min(self.dt_images[i] * self.dt_grow, self.dt_max)
153
+ else: # Energy increased (bad)
154
+ # Decrease time step and reset velocity
155
+ self.dt_images[i] = max(self.dt_images[i] * self.dt_shrink, self.dt_min)
156
+ new_v = np.zeros_like(velocity) # Reset velocity completely
157
+
158
+ # Store results
159
+ new_velocities.append(new_v)
160
+ steps.append(step)
161
+
162
+ return new_velocities, steps
163
+
164
+ def QuickMin_NEB_calc(self, geometry_num_list, total_force_list, pre_total_velocity,
165
+ optimize_num, total_velocity, cos_list,
166
+ biased_energy_list, pre_biased_energy_list, pre_geom):
167
+ """Calculate step using QuickMin for NEB.
168
+
169
+ Parameters:
170
+ ----------
171
+ geometry_num_list : ndarray
172
+ Current geometry coordinates for all images
173
+ total_force_list : ndarray
174
+ Current forces for all images
175
+ pre_total_velocity : ndarray
176
+ Previous velocities for all images
177
+ optimize_num : int
178
+ Current optimization iteration number
179
+ total_velocity : ndarray
180
+ Current velocities for all images
181
+ cos_list : ndarray
182
+ Cosines between adjacent images
183
+ biased_energy_list : ndarray
184
+ Current energy for all images
185
+ pre_biased_energy_list : ndarray
186
+ Previous energy for all images
187
+ pre_geom : ndarray
188
+ Previous geometry coordinates for all images
189
+
190
+ Returns:
191
+ -------
192
+ ndarray
193
+ Updated geometry coordinates for all images
194
+ """
195
+ print(f"\n{'='*50}\nNEB-QuickMin Iteration {self.iter}\n{'='*50}")
196
+
197
+ # Get number of images
198
+ num_images = len(geometry_num_list)
199
+
200
+ # Initialize data structures if first iteration
201
+ if self.Initialization:
202
+ self.initialize_data(num_images)
203
+ self.Initialization = False
204
+ print("First iteration - initializing QuickMin")
205
+
206
+ # Initialize velocities from pre_total_velocity if available
207
+ if pre_total_velocity is not None and len(pre_total_velocity) >= num_images:
208
+ self.velocities = [vel.copy() if vel is not None else np.zeros_like(geometry_num_list[i])
209
+ for i, vel in enumerate(pre_total_velocity[:num_images])]
210
+
211
+ # Print current time steps
212
+ min_dt = np.min(self.dt_images)
213
+ max_dt = np.max(self.dt_images)
214
+ avg_dt = np.mean(self.dt_images)
215
+ print(f"QuickMin time steps - range: [{min_dt:.4f}, {max_dt:.4f}], avg: {avg_dt:.4f}")
216
+
217
+ # Update velocities and calculate steps
218
+ new_velocities, move_vectors = self.update_velocities_and_positions(
219
+ geometry_num_list, total_force_list, biased_energy_list
220
+ )
221
+
222
+ # Apply trust region correction if TR_NEB is provided
223
+ if self.TR_NEB:
224
+ move_vectors = self.TR_NEB.TR_calc(geometry_num_list, total_force_list, move_vectors,
225
+ biased_energy_list, pre_biased_energy_list, pre_geom)
226
+
227
+ # Store current data for next iteration
228
+ for i in range(num_images):
229
+ self.velocities[i] = new_velocities[i]
230
+ self.prev_forces[i] = total_force_list[i].copy()
231
+ self.prev_positions[i] = geometry_num_list[i].copy()
232
+ self.prev_energies[i] = biased_energy_list[i]
233
+
234
+ # Update geometry using move vectors
235
+ new_geometry = []
236
+ for i, (geom, move) in enumerate(zip(geometry_num_list, move_vectors)):
237
+ new_geom = geom + move
238
+ new_geometry.append(new_geom)
239
+
240
+ # Convert to numpy array and apply unit conversion
241
+ new_geometry = np.array(new_geometry)
242
+ new_geometry *= self.bohr2angstroms
243
+
244
+ self.iter += 1
245
+ return new_geometry
@@ -0,0 +1,75 @@
1
+ import numpy as np
2
+ import copy
3
+
4
+
5
+
6
+ class RADAM:
7
+ def __init__(self, **config):
8
+ #arXiv:1908.03265v4
9
+ self.adam_count = 1
10
+ self.DELTA = 0.03
11
+ self.beta_m = 0.9
12
+ self.beta_v = 0.999
13
+ self.Epsilon = 1e-12
14
+ self.Initialization = True
15
+ self.config = config
16
+ self.hessian = None
17
+ self.bias_hessian = None
18
+
19
+ return
20
+
21
+ 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=[]):
22
+ print("RADAM")
23
+ if self.Initialization:
24
+ self.adam_m = geom_num_list * 0.0
25
+ self.adam_v = geom_num_list * 0.0
26
+ self.Initialization = False
27
+ rho_inf = 2.0 / (1.0- self.beta_v) - 1.0
28
+
29
+ adam_count = self.adam_count
30
+ adam_m = self.adam_m
31
+ adam_v = self.adam_v
32
+ new_adam_m = adam_m*0.0
33
+ new_adam_v = adam_v*0.0
34
+
35
+ new_adam_m_hat = []
36
+ new_adam_v_hat = []
37
+ for i in range(len(geom_num_list)):
38
+ new_adam_m[i] = copy.copy(self.beta_m*adam_m[i] + (1.0-self.beta_m)*(B_g[i]))
39
+ new_adam_v[i] = copy.copy((self.beta_v*adam_v[i]) + (1.0-self.beta_v)*(B_g[i] - new_adam_m[i])**2) + self.Epsilon
40
+ new_adam_m_hat.append(np.array(new_adam_m[i], dtype="float64")/(1.0-self.beta_m**adam_count))
41
+ new_adam_v_hat.append(np.array(new_adam_v[i], dtype="float64")/(1.0-self.beta_v**adam_count))
42
+ rho = rho_inf - (2.0*adam_count*self.beta_v**adam_count)/(1.0 -self.beta_v**adam_count)
43
+
44
+ move_vector = []
45
+ if rho > 4.0:
46
+ l_alpha = []
47
+ for j in range(len(new_adam_v)):
48
+ l_alpha.append(np.sqrt((abs(1.0 - self.beta_v**adam_count))/new_adam_v[j]))
49
+ l_alpha = np.array(l_alpha, dtype="float64")
50
+ r = np.sqrt(((rho-4.0)*(rho-2.0)*rho_inf)/((rho_inf-4.0)*(rho_inf-2.0)*rho))
51
+ for i in range(len(geom_num_list)):
52
+ move_vector.append(self.DELTA*r*new_adam_m_hat[i]*l_alpha[i])
53
+ else:
54
+ for i in range(len(geom_num_list)):
55
+ move_vector.append(self.DELTA*new_adam_m_hat[i])
56
+
57
+ self.adam_m = new_adam_m
58
+ self.adam_v = new_adam_v
59
+ self.adam_count += 1
60
+
61
+ return move_vector#Bohr.
62
+
63
+ def set_hessian(self, hessian):
64
+ self.hessian = hessian
65
+ return
66
+
67
+ def set_bias_hessian(self, bias_hessian):
68
+ self.bias_hessian = bias_hessian
69
+ return
70
+
71
+ def get_hessian(self):
72
+ return self.hessian
73
+
74
+ def get_bias_hessian(self):
75
+ return self.bias_hessian
@@ -0,0 +1,302 @@
1
+ import numpy as np
2
+ from abc import ABC, abstractmethod
3
+ from multioptpy.Optimizer import trust_radius_neb
4
+ from multioptpy.Optimizer.fire_neb import FIREOptimizer
5
+ from scipy.signal import argrelextrema
6
+ from multioptpy.Optimizer import rsirfo
7
+ import os
8
+ import copy
9
+
10
+ class OptimizationAlgorithm(ABC):
11
+ """Base class for optimization algorithms"""
12
+
13
+ @abstractmethod
14
+ def optimize(self, geometry_num_list, total_force_list, **kwargs):
15
+ """Execute optimization step"""
16
+ pass
17
+
18
+ def _load_or_init_hessian(self, num, natoms, config):
19
+ """Load existing Hessian or initialize identity matrix"""
20
+ hessian_file = os.path.join(config.NEB_FOLDER_DIRECTORY, f"tmp_hessian_{num}.npy")
21
+ if os.path.exists(hessian_file):
22
+ return np.load(hessian_file)
23
+ else:
24
+ # print(f"Warning: Hessian file {hessian_file} not found. Using identity matrix.")
25
+ return np.eye(3 * natoms)
26
+
27
+ def _setup_rfo_optimizer(self, num, total_nodes, optimize_num, is_qsm=False):
28
+ """Configure RSIRFO instance based on node type"""
29
+ is_endpoint = (num == 0 or num == total_nodes - 1)
30
+
31
+ if is_endpoint:
32
+ # Endpoints: Minimize (order 0), larger trust radius
33
+ OPT = rsirfo.RSIRFO(method="rsirfo_fsb", saddle_order=0, trust_radius=0.5)
34
+ else:
35
+ # Intermediate
36
+ # QSM usually targets saddle order 0 (path finding), NEB might target saddle order 1
37
+ saddle_order = 0 if is_qsm else 1
38
+ OPT = rsirfo.RSIRFO(method="rsirfo_bofill", saddle_order=saddle_order, trust_radius=0.1)
39
+
40
+ OPT.iteration = optimize_num
41
+ return OPT
42
+
43
+ def _apply_ayala_hessian_update(self, hessian, num, total_nodes, geometry_num_list,
44
+ biased_energy_list, total_force_list, STRING_FORCE_CALC):
45
+ """
46
+ Common Method: Update Hessian using Quintic Polynomial Fit (Ayala Stage 1).
47
+ Checks if STRING_FORCE_CALC supports the required methods.
48
+ """
49
+ # Skip endpoints
50
+ if num == 0 or num == total_nodes - 1:
51
+ return hessian
52
+
53
+ # Check if the Force Calculator supports Ayala methods
54
+ if not hasattr(STRING_FORCE_CALC, 'get_tau') or not hasattr(STRING_FORCE_CALC, 'calculate_gamma'):
55
+ return hessian
56
+
57
+ tangent = STRING_FORCE_CALC.get_tau(num)
58
+
59
+ # Extract local triplet safely
60
+ start_idx = max(0, num - 1)
61
+ end_idx = min(len(geometry_num_list), num + 2)
62
+
63
+ gamma = STRING_FORCE_CALC.calculate_gamma(
64
+ geometry_num_list[start_idx:end_idx],
65
+ biased_energy_list[start_idx:end_idx],
66
+ total_force_list[start_idx:end_idx],
67
+ tangent
68
+ )
69
+
70
+ # Add curvature along the tangent direction
71
+ # H_new = H_old + gamma * |t><t|
72
+ hessian += gamma * np.outer(tangent, tangent)
73
+
74
+ return hessian
75
+
76
+ def _limit_step_size(self, move_vec, is_endpoint):
77
+ """Limit the norm of the movement vector"""
78
+ move_vec_norm = np.linalg.norm(move_vec)
79
+ limit = 0.2 if is_endpoint else 0.1
80
+
81
+ if move_vec_norm > 1e-8:
82
+ return move_vec / move_vec_norm * min(limit, move_vec_norm)
83
+ return move_vec
84
+
85
+
86
+ class RFOOptimizer(OptimizationAlgorithm):
87
+ """Rational Function Optimization (RFO) optimizer (Standard NEB)"""
88
+
89
+ def __init__(self, config):
90
+ self.config = config
91
+ self.NEB_TR = trust_radius_neb.TR_NEB(
92
+ NEB_FOLDER_DIRECTORY=config.NEB_FOLDER_DIRECTORY,
93
+ fix_init_edge=config.fix_init_edge,
94
+ fix_end_edge=config.fix_end_edge,
95
+ apply_convergence_criteria=config.apply_convergence_criteria
96
+ )
97
+ self.apply_ts_opt = True
98
+ self.ratio_of_rfo_step = getattr(config, "ratio_of_rfo_step", 0.5)
99
+
100
+ def set_apply_ts_opt(self, apply_ts_opt):
101
+ self.apply_ts_opt = apply_ts_opt
102
+
103
+ def optimize(self, geometry_num_list, total_force_list, prev_geometry_num_list,
104
+ prev_total_force_list, optimize_num, biased_energy_list,
105
+ pre_biased_energy_list, pre_total_velocity, total_velocity,
106
+ cos_list, pre_geom, STRING_FORCE_CALC):
107
+
108
+ natoms = len(geometry_num_list[0])
109
+ total_nodes = len(geometry_num_list)
110
+
111
+ # 1. Calc Force
112
+ proj_total_force_list = STRING_FORCE_CALC.calc_force(
113
+ geometry_num_list, biased_energy_list, total_force_list, optimize_num, self.config.element_list)
114
+
115
+ maxima_indices = argrelextrema(biased_energy_list, np.greater)[0]
116
+ rfo_delta_list = []
117
+
118
+ for num, total_force in enumerate(total_force_list):
119
+ # A. Load Hessian
120
+ hessian = self._load_or_init_hessian(num, natoms, self.config)
121
+
122
+ # B. Setup Optimizer
123
+ # Note: Standard RFOOptimizer logic for 'intermediate' nodes differs slightly (saddle_order=1)
124
+ # We preserve the original logic here manually or via the helper with is_qsm=False
125
+ if num == 0 or num == total_nodes - 1:
126
+ OPT = rsirfo.RSIRFO(method="rsirfo_fsb", saddle_order=0, trust_radius=0.2)
127
+ else:
128
+ OPT = rsirfo.RSIRFO(method="rsirfo_bofill", saddle_order=0, trust_radius=0.1)
129
+ if num in maxima_indices and self.apply_ts_opt:
130
+ pass
131
+ else:
132
+ OPT.switch_NEB_mode()
133
+
134
+ OPT.iteration = optimize_num
135
+ OPT.set_bias_hessian(np.zeros((3*natoms, 3*natoms)))
136
+
137
+ # C. Hessian Processing
138
+
139
+ # [ADDED] Apply Ayala Curvature Correction if available
140
+ hessian = self._apply_ayala_hessian_update(
141
+ hessian, num, total_nodes, geometry_num_list,
142
+ biased_energy_list, total_force_list, STRING_FORCE_CALC
143
+ )
144
+
145
+ OPT.set_hessian(hessian)
146
+
147
+ # D. Prepare Steps
148
+ if optimize_num == 0:
149
+ OPT.Initialization = True
150
+ pre_B_g, pre_geom_node = None, None
151
+ else:
152
+ OPT.Initialization = False
153
+ pre_B_g = prev_total_force_list[num].reshape(-1, 1)
154
+ pre_geom_node = prev_geometry_num_list[num].reshape(-1, 1)
155
+
156
+ curr_geom_node = geometry_num_list[num].reshape(-1, 1)
157
+ B_g = total_force.reshape(-1, 1)
158
+
159
+ # E. Run
160
+ move_vec = OPT.run(curr_geom_node, B_g, pre_B_g, pre_geom_node, 0.0, 0.0, [], [], B_g, pre_B_g)
161
+
162
+ # F. Limit Step
163
+ move_vec = self._limit_step_size(move_vec, num == 0 or num == total_nodes - 1)
164
+
165
+ rfo_delta_list.append(move_vec.reshape(-1, 3))
166
+
167
+ # G. Save
168
+ new_hessian = OPT.get_hessian()
169
+ np.save(os.path.join(self.config.NEB_FOLDER_DIRECTORY, f"tmp_hessian_{num}.npy"), new_hessian)
170
+
171
+ # 3. TR Calc
172
+ rfo_move_vector_list = self.NEB_TR.TR_calc(
173
+ geometry_num_list, total_force_list, rfo_delta_list,
174
+ biased_energy_list, pre_biased_energy_list, pre_geom
175
+ )
176
+
177
+ # 4. FIRE
178
+ fire_optimizer = FIREOptimizer(self.config)
179
+ tmp_new_geom = fire_optimizer.optimize(
180
+ geometry_num_list, proj_total_force_list,
181
+ pre_total_velocity, optimize_num, total_velocity,
182
+ cos_list, biased_energy_list, pre_biased_energy_list, pre_geom
183
+ )
184
+ tmp_new_geom = np.array(tmp_new_geom, dtype="float64") / self.config.bohr2angstroms
185
+ fire_move_vector_list = tmp_new_geom - geometry_num_list
186
+
187
+ # 5. Combine
188
+ move_vector_list = []
189
+ for i in range(len(geometry_num_list)):
190
+ if i == 0 or i == len(geometry_num_list) - 1:
191
+ move_vector_list.append(-1.0 * rfo_move_vector_list[i])
192
+ else:
193
+ move_vector_list.append((1.0 - self.ratio_of_rfo_step) * fire_move_vector_list[i] -
194
+ self.ratio_of_rfo_step * rfo_move_vector_list[i])
195
+
196
+ move_vector_list = np.array(move_vector_list, dtype="float64")
197
+ new_geometry_list = (geometry_num_list + move_vector_list) * self.config.bohr2angstroms
198
+
199
+ return new_geometry_list
200
+
201
+
202
+ class RFOQSMOptimizer(OptimizationAlgorithm):
203
+ """Rational Function Optimization (RFO) optimizer for QSM (Quadratic String Method)"""
204
+
205
+ def __init__(self, config):
206
+ self.config = config
207
+ self.NEB_TR = trust_radius_neb.TR_NEB(
208
+ NEB_FOLDER_DIRECTORY=config.NEB_FOLDER_DIRECTORY,
209
+ fix_init_edge=config.fix_init_edge,
210
+ fix_end_edge=config.fix_end_edge,
211
+ apply_convergence_criteria=config.apply_convergence_criteria
212
+ )
213
+ self.ratio_of_rfo_step = getattr(config, "ratio_of_rfo_step", 0.5)
214
+ self.is_qsmv2 = getattr(config, "qsmv2", False)
215
+
216
+ def optimize(self, geometry_num_list, total_force_list, prev_geometry_num_list,
217
+ prev_total_force_list, optimize_num, biased_energy_list,
218
+ pre_biased_energy_list, pre_total_velocity, total_velocity,
219
+ cos_list, pre_geom, STRING_FORCE_CALC):
220
+
221
+ natoms = len(geometry_num_list[0])
222
+ total_nodes = len(geometry_num_list)
223
+
224
+ # 1. Calc Force
225
+ proj_total_force_list = STRING_FORCE_CALC.calc_force(
226
+ geometry_num_list, biased_energy_list, total_force_list, optimize_num, self.config.element_list)
227
+
228
+ rfo_delta_list = []
229
+
230
+ for num, total_force in enumerate(total_force_list):
231
+ # A. Load Hessian
232
+ hessian = self._load_or_init_hessian(num, natoms, self.config)
233
+
234
+ # B. Setup Optimizer (QSM mode uses saddle_order=0 for intermediates usually)
235
+ OPT = self._setup_rfo_optimizer(num, total_nodes, optimize_num, is_qsm=True)
236
+ OPT.set_bias_hessian(np.zeros((3*natoms, 3*natoms)))
237
+
238
+ # C. Hessian Processing
239
+ hessian = STRING_FORCE_CALC.calc_proj_hess(hessian, num, geometry_num_list)
240
+
241
+ # [ADDED] Apply Ayala Curvature Correction (Use the base class method)
242
+ if self.is_qsmv2:
243
+ hessian = self._apply_ayala_hessian_update(
244
+ hessian, num, total_nodes, geometry_num_list,
245
+ biased_energy_list, total_force_list, STRING_FORCE_CALC
246
+ )
247
+
248
+ OPT.set_hessian(hessian)
249
+
250
+ # D. Prepare Steps
251
+ if optimize_num == 0:
252
+ OPT.Initialization = True
253
+ pre_B_g, pre_geom_node = None, None
254
+ else:
255
+ OPT.Initialization = False
256
+ pre_B_g = prev_total_force_list[num].reshape(-1, 1)
257
+ pre_geom_node = prev_geometry_num_list[num].reshape(-1, 1)
258
+
259
+ curr_geom_node = geometry_num_list[num].reshape(-1, 1)
260
+ B_g = total_force.reshape(-1, 1)
261
+
262
+ # E. Run
263
+ move_vec = OPT.run(curr_geom_node, B_g, pre_B_g, pre_geom_node, 0.0, 0.0, [], [], B_g, pre_B_g)
264
+
265
+ # F. Limit Step
266
+ move_vec = self._limit_step_size(move_vec, num == 0 or num == total_nodes - 1)
267
+
268
+ rfo_delta_list.append(move_vec.reshape(-1, 3))
269
+
270
+ # G. Save
271
+ new_hessian = OPT.get_hessian()
272
+ np.save(os.path.join(self.config.NEB_FOLDER_DIRECTORY, f"tmp_hessian_{num}.npy"), new_hessian)
273
+
274
+ # 3. TR Calc
275
+ rfo_move_vector_list = self.NEB_TR.TR_calc(
276
+ geometry_num_list, total_force_list, rfo_delta_list,
277
+ biased_energy_list, pre_biased_energy_list, pre_geom
278
+ )
279
+
280
+ # 4. FIRE
281
+ fire_optimizer = FIREOptimizer(self.config)
282
+ tmp_new_geom = fire_optimizer.optimize(
283
+ geometry_num_list, proj_total_force_list,
284
+ pre_total_velocity, optimize_num, total_velocity,
285
+ cos_list, biased_energy_list, pre_biased_energy_list, pre_geom
286
+ )
287
+ tmp_new_geom = np.array(tmp_new_geom, dtype="float64") / self.config.bohr2angstroms
288
+ fire_move_vector_list = tmp_new_geom - geometry_num_list
289
+
290
+ # 5. Combine
291
+ move_vector_list = []
292
+ for i in range(total_nodes):
293
+ if i == 0 or i == total_nodes - 1:
294
+ move_vector_list.append(fire_move_vector_list[i])
295
+ else:
296
+ move_vector_list.append((1.0 - self.ratio_of_rfo_step) * fire_move_vector_list[i] -
297
+ self.ratio_of_rfo_step * rfo_move_vector_list[i])
298
+
299
+ move_vector_list = np.array(move_vector_list, dtype="float64")
300
+ new_geometry_list = (geometry_num_list + move_vector_list) * self.config.bohr2angstroms
301
+
302
+ return new_geometry_list