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.
- multioptpy/Calculator/__init__.py +0 -0
- multioptpy/Calculator/ase_calculation_tools.py +424 -0
- multioptpy/Calculator/ase_tools/__init__.py +0 -0
- multioptpy/Calculator/ase_tools/fairchem.py +28 -0
- multioptpy/Calculator/ase_tools/gamess.py +19 -0
- multioptpy/Calculator/ase_tools/gaussian.py +165 -0
- multioptpy/Calculator/ase_tools/mace.py +28 -0
- multioptpy/Calculator/ase_tools/mopac.py +19 -0
- multioptpy/Calculator/ase_tools/nwchem.py +31 -0
- multioptpy/Calculator/ase_tools/orca.py +22 -0
- multioptpy/Calculator/ase_tools/pygfn0.py +37 -0
- multioptpy/Calculator/dxtb_calculation_tools.py +344 -0
- multioptpy/Calculator/emt_calculation_tools.py +458 -0
- multioptpy/Calculator/gpaw_calculation_tools.py +183 -0
- multioptpy/Calculator/lj_calculation_tools.py +314 -0
- multioptpy/Calculator/psi4_calculation_tools.py +334 -0
- multioptpy/Calculator/pwscf_calculation_tools.py +189 -0
- multioptpy/Calculator/pyscf_calculation_tools.py +327 -0
- multioptpy/Calculator/sqm1_calculation_tools.py +611 -0
- multioptpy/Calculator/sqm2_calculation_tools.py +376 -0
- multioptpy/Calculator/tblite_calculation_tools.py +352 -0
- multioptpy/Calculator/tersoff_calculation_tools.py +818 -0
- multioptpy/Constraint/__init__.py +0 -0
- multioptpy/Constraint/constraint_condition.py +834 -0
- multioptpy/Coordinate/__init__.py +0 -0
- multioptpy/Coordinate/polar_coordinate.py +199 -0
- multioptpy/Coordinate/redundant_coordinate.py +638 -0
- multioptpy/IRC/__init__.py +0 -0
- multioptpy/IRC/converge_criteria.py +28 -0
- multioptpy/IRC/dvv.py +544 -0
- multioptpy/IRC/euler.py +439 -0
- multioptpy/IRC/hpc.py +564 -0
- multioptpy/IRC/lqa.py +540 -0
- multioptpy/IRC/modekill.py +662 -0
- multioptpy/IRC/rk4.py +579 -0
- multioptpy/Interpolation/__init__.py +0 -0
- multioptpy/Interpolation/adaptive_interpolation.py +283 -0
- multioptpy/Interpolation/binomial_interpolation.py +179 -0
- multioptpy/Interpolation/geodesic_interpolation.py +785 -0
- multioptpy/Interpolation/interpolation.py +156 -0
- multioptpy/Interpolation/linear_interpolation.py +473 -0
- multioptpy/Interpolation/savitzky_golay_interpolation.py +252 -0
- multioptpy/Interpolation/spline_interpolation.py +353 -0
- multioptpy/MD/__init__.py +0 -0
- multioptpy/MD/thermostat.py +185 -0
- multioptpy/MEP/__init__.py +0 -0
- multioptpy/MEP/pathopt_bneb_force.py +443 -0
- multioptpy/MEP/pathopt_dmf_force.py +448 -0
- multioptpy/MEP/pathopt_dneb_force.py +130 -0
- multioptpy/MEP/pathopt_ewbneb_force.py +207 -0
- multioptpy/MEP/pathopt_gpneb_force.py +512 -0
- multioptpy/MEP/pathopt_lup_force.py +113 -0
- multioptpy/MEP/pathopt_neb_force.py +225 -0
- multioptpy/MEP/pathopt_nesb_force.py +205 -0
- multioptpy/MEP/pathopt_om_force.py +153 -0
- multioptpy/MEP/pathopt_qsm_force.py +174 -0
- multioptpy/MEP/pathopt_qsmv2_force.py +304 -0
- multioptpy/ModelFunction/__init__.py +7 -0
- multioptpy/ModelFunction/avoiding_model_function.py +29 -0
- multioptpy/ModelFunction/binary_image_ts_search_model_function.py +47 -0
- multioptpy/ModelFunction/conical_model_function.py +26 -0
- multioptpy/ModelFunction/opt_meci.py +50 -0
- multioptpy/ModelFunction/opt_mesx.py +47 -0
- multioptpy/ModelFunction/opt_mesx_2.py +49 -0
- multioptpy/ModelFunction/seam_model_function.py +27 -0
- multioptpy/ModelHessian/__init__.py +0 -0
- multioptpy/ModelHessian/approx_hessian.py +147 -0
- multioptpy/ModelHessian/calc_params.py +227 -0
- multioptpy/ModelHessian/fischer.py +236 -0
- multioptpy/ModelHessian/fischerd3.py +360 -0
- multioptpy/ModelHessian/fischerd4.py +398 -0
- multioptpy/ModelHessian/gfn0xtb.py +633 -0
- multioptpy/ModelHessian/gfnff.py +709 -0
- multioptpy/ModelHessian/lindh.py +165 -0
- multioptpy/ModelHessian/lindh2007d2.py +707 -0
- multioptpy/ModelHessian/lindh2007d3.py +822 -0
- multioptpy/ModelHessian/lindh2007d4.py +1030 -0
- multioptpy/ModelHessian/morse.py +106 -0
- multioptpy/ModelHessian/schlegel.py +144 -0
- multioptpy/ModelHessian/schlegeld3.py +322 -0
- multioptpy/ModelHessian/schlegeld4.py +559 -0
- multioptpy/ModelHessian/shortrange.py +346 -0
- multioptpy/ModelHessian/swartd2.py +496 -0
- multioptpy/ModelHessian/swartd3.py +706 -0
- multioptpy/ModelHessian/swartd4.py +918 -0
- multioptpy/ModelHessian/tshess.py +40 -0
- multioptpy/Optimizer/QHAdam.py +61 -0
- multioptpy/Optimizer/__init__.py +0 -0
- multioptpy/Optimizer/abc_fire.py +83 -0
- multioptpy/Optimizer/adabelief.py +58 -0
- multioptpy/Optimizer/adabound.py +68 -0
- multioptpy/Optimizer/adadelta.py +65 -0
- multioptpy/Optimizer/adaderivative.py +56 -0
- multioptpy/Optimizer/adadiff.py +68 -0
- multioptpy/Optimizer/adafactor.py +70 -0
- multioptpy/Optimizer/adam.py +65 -0
- multioptpy/Optimizer/adamax.py +62 -0
- multioptpy/Optimizer/adamod.py +83 -0
- multioptpy/Optimizer/adamw.py +65 -0
- multioptpy/Optimizer/adiis.py +523 -0
- multioptpy/Optimizer/afire_neb.py +282 -0
- multioptpy/Optimizer/block_hessian_update.py +709 -0
- multioptpy/Optimizer/c2diis.py +491 -0
- multioptpy/Optimizer/component_wise_scaling.py +405 -0
- multioptpy/Optimizer/conjugate_gradient.py +82 -0
- multioptpy/Optimizer/conjugate_gradient_neb.py +345 -0
- multioptpy/Optimizer/coordinate_locking.py +405 -0
- multioptpy/Optimizer/dic_rsirfo.py +1015 -0
- multioptpy/Optimizer/ediis.py +417 -0
- multioptpy/Optimizer/eve.py +76 -0
- multioptpy/Optimizer/fastadabelief.py +61 -0
- multioptpy/Optimizer/fire.py +77 -0
- multioptpy/Optimizer/fire2.py +249 -0
- multioptpy/Optimizer/fire_neb.py +92 -0
- multioptpy/Optimizer/gan_step.py +486 -0
- multioptpy/Optimizer/gdiis.py +609 -0
- multioptpy/Optimizer/gediis.py +203 -0
- multioptpy/Optimizer/geodesic_step.py +433 -0
- multioptpy/Optimizer/gpmin.py +633 -0
- multioptpy/Optimizer/gpr_step.py +364 -0
- multioptpy/Optimizer/gradientdescent.py +78 -0
- multioptpy/Optimizer/gradientdescent_neb.py +52 -0
- multioptpy/Optimizer/hessian_update.py +433 -0
- multioptpy/Optimizer/hybrid_rfo.py +998 -0
- multioptpy/Optimizer/kdiis.py +625 -0
- multioptpy/Optimizer/lars.py +21 -0
- multioptpy/Optimizer/lbfgs.py +253 -0
- multioptpy/Optimizer/lbfgs_neb.py +355 -0
- multioptpy/Optimizer/linesearch.py +236 -0
- multioptpy/Optimizer/lookahead.py +40 -0
- multioptpy/Optimizer/nadam.py +64 -0
- multioptpy/Optimizer/newton.py +200 -0
- multioptpy/Optimizer/prodigy.py +70 -0
- multioptpy/Optimizer/purtubation.py +16 -0
- multioptpy/Optimizer/quickmin_neb.py +245 -0
- multioptpy/Optimizer/radam.py +75 -0
- multioptpy/Optimizer/rfo_neb.py +302 -0
- multioptpy/Optimizer/ric_rfo.py +842 -0
- multioptpy/Optimizer/rl_step.py +627 -0
- multioptpy/Optimizer/rmspropgrave.py +65 -0
- multioptpy/Optimizer/rsirfo.py +1647 -0
- multioptpy/Optimizer/rsprfo.py +1056 -0
- multioptpy/Optimizer/sadam.py +60 -0
- multioptpy/Optimizer/samsgrad.py +63 -0
- multioptpy/Optimizer/tr_lbfgs.py +678 -0
- multioptpy/Optimizer/trim.py +273 -0
- multioptpy/Optimizer/trust_radius.py +207 -0
- multioptpy/Optimizer/trust_radius_neb.py +121 -0
- multioptpy/Optimizer/yogi.py +60 -0
- multioptpy/OtherMethod/__init__.py +0 -0
- multioptpy/OtherMethod/addf.py +1150 -0
- multioptpy/OtherMethod/dimer.py +895 -0
- multioptpy/OtherMethod/elastic_image_pair.py +629 -0
- multioptpy/OtherMethod/modelfunction.py +456 -0
- multioptpy/OtherMethod/newton_traj.py +454 -0
- multioptpy/OtherMethod/twopshs.py +1095 -0
- multioptpy/PESAnalyzer/__init__.py +0 -0
- multioptpy/PESAnalyzer/calc_irc_curvature.py +125 -0
- multioptpy/PESAnalyzer/cmds_analysis.py +152 -0
- multioptpy/PESAnalyzer/koopman_analysis.py +268 -0
- multioptpy/PESAnalyzer/pca_analysis.py +314 -0
- multioptpy/Parameters/__init__.py +0 -0
- multioptpy/Parameters/atomic_mass.py +20 -0
- multioptpy/Parameters/atomic_number.py +22 -0
- multioptpy/Parameters/covalent_radii.py +44 -0
- multioptpy/Parameters/d2.py +61 -0
- multioptpy/Parameters/d3.py +63 -0
- multioptpy/Parameters/d4.py +103 -0
- multioptpy/Parameters/dreiding.py +34 -0
- multioptpy/Parameters/gfn0xtb_param.py +137 -0
- multioptpy/Parameters/gfnff_param.py +315 -0
- multioptpy/Parameters/gnb.py +104 -0
- multioptpy/Parameters/parameter.py +22 -0
- multioptpy/Parameters/uff.py +72 -0
- multioptpy/Parameters/unit_values.py +20 -0
- multioptpy/Potential/AFIR_potential.py +55 -0
- multioptpy/Potential/LJ_repulsive_potential.py +345 -0
- multioptpy/Potential/__init__.py +0 -0
- multioptpy/Potential/anharmonic_keep_potential.py +28 -0
- multioptpy/Potential/asym_elllipsoidal_potential.py +718 -0
- multioptpy/Potential/electrostatic_potential.py +69 -0
- multioptpy/Potential/flux_potential.py +30 -0
- multioptpy/Potential/gaussian_potential.py +101 -0
- multioptpy/Potential/idpp.py +516 -0
- multioptpy/Potential/keep_angle_potential.py +146 -0
- multioptpy/Potential/keep_dihedral_angle_potential.py +105 -0
- multioptpy/Potential/keep_outofplain_angle_potential.py +70 -0
- multioptpy/Potential/keep_potential.py +99 -0
- multioptpy/Potential/mechano_force_potential.py +74 -0
- multioptpy/Potential/nanoreactor_potential.py +52 -0
- multioptpy/Potential/potential.py +896 -0
- multioptpy/Potential/spacer_model_potential.py +221 -0
- multioptpy/Potential/switching_potential.py +258 -0
- multioptpy/Potential/universal_potential.py +34 -0
- multioptpy/Potential/value_range_potential.py +36 -0
- multioptpy/Potential/void_point_potential.py +25 -0
- multioptpy/SQM/__init__.py +0 -0
- multioptpy/SQM/sqm1/__init__.py +0 -0
- multioptpy/SQM/sqm1/sqm1_core.py +1792 -0
- multioptpy/SQM/sqm2/__init__.py +0 -0
- multioptpy/SQM/sqm2/calc_tools.py +95 -0
- multioptpy/SQM/sqm2/sqm2_basis.py +850 -0
- multioptpy/SQM/sqm2/sqm2_bond.py +119 -0
- multioptpy/SQM/sqm2/sqm2_core.py +303 -0
- multioptpy/SQM/sqm2/sqm2_data.py +1229 -0
- multioptpy/SQM/sqm2/sqm2_disp.py +65 -0
- multioptpy/SQM/sqm2/sqm2_eeq.py +243 -0
- multioptpy/SQM/sqm2/sqm2_overlapint.py +704 -0
- multioptpy/SQM/sqm2/sqm2_qm.py +578 -0
- multioptpy/SQM/sqm2/sqm2_rep.py +66 -0
- multioptpy/SQM/sqm2/sqm2_srb.py +70 -0
- multioptpy/Thermo/__init__.py +0 -0
- multioptpy/Thermo/normal_mode_analyzer.py +865 -0
- multioptpy/Utils/__init__.py +0 -0
- multioptpy/Utils/bond_connectivity.py +264 -0
- multioptpy/Utils/calc_tools.py +884 -0
- multioptpy/Utils/oniom.py +96 -0
- multioptpy/Utils/pbc.py +48 -0
- multioptpy/Utils/riemann_curvature.py +208 -0
- multioptpy/Utils/symmetry_analyzer.py +482 -0
- multioptpy/Visualization/__init__.py +0 -0
- multioptpy/Visualization/visualization.py +156 -0
- multioptpy/WFAnalyzer/MO_analysis.py +104 -0
- multioptpy/WFAnalyzer/__init__.py +0 -0
- multioptpy/Wrapper/__init__.py +0 -0
- multioptpy/Wrapper/autots.py +1239 -0
- multioptpy/Wrapper/ieip_wrapper.py +93 -0
- multioptpy/Wrapper/md_wrapper.py +92 -0
- multioptpy/Wrapper/neb_wrapper.py +94 -0
- multioptpy/Wrapper/optimize_wrapper.py +76 -0
- multioptpy/__init__.py +5 -0
- multioptpy/entrypoints.py +916 -0
- multioptpy/fileio.py +660 -0
- multioptpy/ieip.py +340 -0
- multioptpy/interface.py +1086 -0
- multioptpy/irc.py +529 -0
- multioptpy/moleculardynamics.py +432 -0
- multioptpy/neb.py +1267 -0
- multioptpy/optimization.py +1553 -0
- multioptpy/optimizer.py +709 -0
- multioptpy-1.20.2.dist-info/METADATA +438 -0
- multioptpy-1.20.2.dist-info/RECORD +246 -0
- multioptpy-1.20.2.dist-info/WHEEL +5 -0
- multioptpy-1.20.2.dist-info/entry_points.txt +9 -0
- multioptpy-1.20.2.dist-info/licenses/LICENSE +674 -0
- 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
|