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,345 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy.linalg import norm
|
|
3
|
+
|
|
4
|
+
from multioptpy.Parameters.parameter import UnitValueLib
|
|
5
|
+
|
|
6
|
+
class ConjugateGradientNEB:
|
|
7
|
+
"""Nonlinear Conjugate Gradient optimizer for Nudged Elastic Band calculations.
|
|
8
|
+
|
|
9
|
+
This implementation provides multiple conjugate gradient update formulas:
|
|
10
|
+
- Fletcher-Reeves (FR): Numerically stable but slower convergence
|
|
11
|
+
- Polak-Ribière (PR): Faster convergence but can be unstable without safeguards
|
|
12
|
+
- Hestenes-Stiefel (HS): Often performs better than FR and PR for nonlinear problems
|
|
13
|
+
- Dai-Yuan (DY): Good global convergence properties and descent direction guarantee
|
|
14
|
+
- Hager-Zhang (HZ): Modern, efficient update with strong theoretical properties
|
|
15
|
+
|
|
16
|
+
All methods include automatic restart strategies for enhanced stability and performance.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, **config):
|
|
20
|
+
# Configuration parameters
|
|
21
|
+
self.config = config
|
|
22
|
+
|
|
23
|
+
# Initialize flags
|
|
24
|
+
self.Initialization = True
|
|
25
|
+
self.iter = 0
|
|
26
|
+
|
|
27
|
+
# Set default parameters
|
|
28
|
+
self.maxstep = config.get("maxstep", 0.1) # Maximum step size
|
|
29
|
+
|
|
30
|
+
# Line search parameters
|
|
31
|
+
self.initial_step_size = config.get("initial_step_size", 0.1) # Initial step size for line search
|
|
32
|
+
self.min_step_size = config.get("min_step_size", 0.01) # Minimum step size
|
|
33
|
+
self.max_step_size = config.get("max_step_size", 0.1) # Maximum step size
|
|
34
|
+
self.step_descent_factor = config.get("step_descent_factor", 0.5) # Step size reduction factor
|
|
35
|
+
|
|
36
|
+
# CG parameters
|
|
37
|
+
# Valid options: "FR", "PR", "HS", "DY", "HZ"
|
|
38
|
+
self.cg_method = config.get("cg_method", "HS") # Conjugate gradient method
|
|
39
|
+
self.restart_cycles = config.get("restart_cycles", 10) # Restart CG every n iterations
|
|
40
|
+
self.restart_threshold = config.get("restart_threshold", 0.2) # Restart if orthogonality drops below this
|
|
41
|
+
|
|
42
|
+
# Storage for previous step data
|
|
43
|
+
self.prev_forces = None # Previous forces for all nodes
|
|
44
|
+
self.prev_directions = None # Previous CG directions for all nodes
|
|
45
|
+
self.prev_positions = None # Previous positions for all nodes
|
|
46
|
+
self.prev_energies = None # Previous energies for all nodes
|
|
47
|
+
|
|
48
|
+
# Node-specific step sizes (adaptive)
|
|
49
|
+
self.node_step_sizes = None
|
|
50
|
+
|
|
51
|
+
# Unit conversion
|
|
52
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
53
|
+
if 'bohr2angstroms' in config:
|
|
54
|
+
self.bohr2angstroms = config['bohr2angstroms']
|
|
55
|
+
|
|
56
|
+
# Compatibility with the original code
|
|
57
|
+
self.TR_NEB = config.get("TR_NEB", None)
|
|
58
|
+
|
|
59
|
+
# Print initialization message based on selected method
|
|
60
|
+
print(f"Initialized Conjugate Gradient NEB optimizer with method={self.cg_method}, maxstep={self.maxstep}")
|
|
61
|
+
if self.cg_method == "FR":
|
|
62
|
+
print("Using Fletcher-Reeves method with periodic restart")
|
|
63
|
+
elif self.cg_method == "PR":
|
|
64
|
+
print("Using Polak-Ribière method with automatic restart")
|
|
65
|
+
elif self.cg_method == "HS":
|
|
66
|
+
print("Using Hestenes-Stiefel method with automatic restart")
|
|
67
|
+
elif self.cg_method == "DY":
|
|
68
|
+
print("Using Dai-Yuan method with strong convergence properties")
|
|
69
|
+
elif self.cg_method == "HZ":
|
|
70
|
+
print("Using Hager-Zhang method with enhanced numerical stability")
|
|
71
|
+
else:
|
|
72
|
+
print(f"Warning: Unknown CG method '{self.cg_method}', falling back to Fletcher-Reeves")
|
|
73
|
+
self.cg_method = "FR"
|
|
74
|
+
|
|
75
|
+
def initialize_data(self, num_nodes):
|
|
76
|
+
"""Initialize data structures for the optimization.
|
|
77
|
+
|
|
78
|
+
Parameters:
|
|
79
|
+
----------
|
|
80
|
+
num_nodes : int
|
|
81
|
+
Number of nodes in the NEB calculation
|
|
82
|
+
"""
|
|
83
|
+
# Initialize storage for previous data
|
|
84
|
+
self.prev_forces = [None] * num_nodes
|
|
85
|
+
self.prev_directions = [None] * num_nodes
|
|
86
|
+
self.prev_positions = [None] * num_nodes
|
|
87
|
+
self.prev_energies = [None] * num_nodes
|
|
88
|
+
|
|
89
|
+
# Initialize node-specific step sizes
|
|
90
|
+
self.node_step_sizes = np.ones(num_nodes) * self.initial_step_size
|
|
91
|
+
|
|
92
|
+
print(f"Initialized CG storage for {num_nodes} nodes")
|
|
93
|
+
|
|
94
|
+
def compute_cg_direction(self, force, node_idx):
|
|
95
|
+
"""Compute the conjugate gradient direction for an node.
|
|
96
|
+
|
|
97
|
+
Parameters:
|
|
98
|
+
----------
|
|
99
|
+
force : ndarray
|
|
100
|
+
Current force vector for the node
|
|
101
|
+
node_idx : int
|
|
102
|
+
Index of the node in the NEB chain
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
-------
|
|
106
|
+
ndarray
|
|
107
|
+
Conjugate gradient direction
|
|
108
|
+
"""
|
|
109
|
+
# If first iteration or no history, return the force direction (steepest descent)
|
|
110
|
+
if self.prev_forces[node_idx] is None:
|
|
111
|
+
return force.copy()
|
|
112
|
+
|
|
113
|
+
# Get previous force and direction
|
|
114
|
+
prev_force = self.prev_forces[node_idx]
|
|
115
|
+
prev_direction = self.prev_directions[node_idx]
|
|
116
|
+
|
|
117
|
+
# Compute change in force (negative gradient)
|
|
118
|
+
force_change = force - prev_force
|
|
119
|
+
|
|
120
|
+
# Flatten vectors for easier dot product calculations
|
|
121
|
+
g = force.flatten()
|
|
122
|
+
g_prev = prev_force.flatten()
|
|
123
|
+
d_prev = prev_direction.flatten()
|
|
124
|
+
y = force_change.flatten()
|
|
125
|
+
|
|
126
|
+
# Compute beta coefficient according to selected method
|
|
127
|
+
beta = 0.0 # Default value
|
|
128
|
+
|
|
129
|
+
# Compute dot products that will be reused
|
|
130
|
+
g_dot_g = np.dot(g, g)
|
|
131
|
+
g_prev_dot_g_prev = np.dot(g_prev, g_prev)
|
|
132
|
+
y_dot_d_prev = np.dot(y, d_prev)
|
|
133
|
+
g_dot_y = np.dot(g, y)
|
|
134
|
+
|
|
135
|
+
# Small constant to avoid division by zero
|
|
136
|
+
epsilon = 1e-10
|
|
137
|
+
|
|
138
|
+
if self.cg_method == "FR": # Fletcher-Reeves
|
|
139
|
+
beta = g_dot_g / max(epsilon, g_prev_dot_g_prev)
|
|
140
|
+
|
|
141
|
+
elif self.cg_method == "PR": # Polak-Ribière
|
|
142
|
+
beta = g_dot_y / max(epsilon, g_prev_dot_g_prev)
|
|
143
|
+
# Apply Polak-Ribière+ modification: max(beta, 0)
|
|
144
|
+
beta = max(0.0, beta)
|
|
145
|
+
|
|
146
|
+
elif self.cg_method == "HS": # Hestenes-Stiefel
|
|
147
|
+
beta = g_dot_y / max(epsilon, y_dot_d_prev)
|
|
148
|
+
# Apply HS+ modification for guaranteed descent direction
|
|
149
|
+
beta = max(0.0, beta)
|
|
150
|
+
|
|
151
|
+
elif self.cg_method == "DY": # Dai-Yuan
|
|
152
|
+
beta = g_dot_g / max(epsilon, y_dot_d_prev)
|
|
153
|
+
# DY method always yields descent directions
|
|
154
|
+
|
|
155
|
+
elif self.cg_method == "HZ": # Hager-Zhang
|
|
156
|
+
y_dot_y = np.dot(y, y)
|
|
157
|
+
g_prev_dot_y = np.dot(g_prev, y)
|
|
158
|
+
|
|
159
|
+
# Hager-Zhang formula
|
|
160
|
+
beta = (g_dot_y - 2 * np.dot(g, y) * np.dot(y, d_prev) / max(epsilon, y_dot_y)) / max(epsilon, y_dot_d_prev)
|
|
161
|
+
|
|
162
|
+
# Apply HZ+ modification
|
|
163
|
+
eta = 0.4 # Parameter from the HZ paper
|
|
164
|
+
beta = max(-eta * g_prev_dot_g_prev / max(epsilon, np.dot(d_prev, d_prev)), beta)
|
|
165
|
+
|
|
166
|
+
# Check orthogonality for restart
|
|
167
|
+
orthogonality = np.abs(np.dot(g, g_prev)) / (norm(g) * norm(g_prev) + epsilon)
|
|
168
|
+
|
|
169
|
+
# Reset beta to zero (restart CG) if:
|
|
170
|
+
# 1. We're at a restart cycle iteration
|
|
171
|
+
# 2. Orthogonality is high (gradient directions are too similar)
|
|
172
|
+
# 3. Beta is negative (not an issue with PR+, HS+, HZ+, but added as safety)
|
|
173
|
+
if (self.iter % self.restart_cycles == 0) or (orthogonality > (1.0 - self.restart_threshold)):
|
|
174
|
+
print(f"Restarting CG for node {node_idx} (orthogonality: {orthogonality:.3f})")
|
|
175
|
+
beta = 0.0
|
|
176
|
+
|
|
177
|
+
# Compute new conjugate direction
|
|
178
|
+
new_direction = force + beta * prev_direction
|
|
179
|
+
|
|
180
|
+
# Ensure we're moving in a descent direction (dot product with force should be positive)
|
|
181
|
+
if np.dot(new_direction.flatten(), g) < 0:
|
|
182
|
+
print(f"Warning: CG direction not a descent direction for node {node_idx}, resetting to steepest descent")
|
|
183
|
+
new_direction = force.copy()
|
|
184
|
+
|
|
185
|
+
return new_direction
|
|
186
|
+
|
|
187
|
+
def adjust_step_sizes(self, energies, prev_energies, forces):
|
|
188
|
+
"""Adjust step sizes for each node based on energy changes.
|
|
189
|
+
|
|
190
|
+
Parameters:
|
|
191
|
+
----------
|
|
192
|
+
energies : list of float
|
|
193
|
+
Current energies for all nodes
|
|
194
|
+
prev_energies : list of float
|
|
195
|
+
Previous energies for all nodes
|
|
196
|
+
forces : list of ndarray
|
|
197
|
+
Current forces for all nodes
|
|
198
|
+
"""
|
|
199
|
+
if prev_energies is None or None in prev_energies:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
for i, (curr_e, prev_e) in enumerate(zip(energies, prev_energies)):
|
|
203
|
+
if prev_e is None:
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
# Calculate energy change
|
|
207
|
+
energy_change = prev_e - curr_e
|
|
208
|
+
force_magnitude = norm(forces[i].flatten())
|
|
209
|
+
|
|
210
|
+
# Adjust step size based on energy change
|
|
211
|
+
if energy_change > 0: # Energy decreased (good)
|
|
212
|
+
# Increase step size, but more conservatively for larger forces
|
|
213
|
+
increase_factor = 1.2 * np.exp(-0.1 * force_magnitude)
|
|
214
|
+
self.node_step_sizes[i] = min(
|
|
215
|
+
self.node_step_sizes[i] * (1.0 + increase_factor),
|
|
216
|
+
self.max_step_size
|
|
217
|
+
)
|
|
218
|
+
print(f"node {i}: Energy decreased by {energy_change:.6e}, increasing step size to {self.node_step_sizes[i]:.4f}")
|
|
219
|
+
else: # Energy increased (bad)
|
|
220
|
+
# Decrease step size more aggressively for larger energy increases
|
|
221
|
+
decrease_factor = self.step_descent_factor * (1.0 + 0.5 * abs(energy_change))
|
|
222
|
+
self.node_step_sizes[i] = max(
|
|
223
|
+
self.node_step_sizes[i] * decrease_factor,
|
|
224
|
+
self.min_step_size
|
|
225
|
+
)
|
|
226
|
+
print(f"node {i}: Energy increased by {abs(energy_change):.6e}, decreasing step size to {self.node_step_sizes[i]:.4f}")
|
|
227
|
+
|
|
228
|
+
def determine_step(self, directions, step_sizes):
|
|
229
|
+
"""Determine step vectors from directions and step sizes, constrained by maxstep.
|
|
230
|
+
|
|
231
|
+
Parameters:
|
|
232
|
+
----------
|
|
233
|
+
directions : list of ndarray
|
|
234
|
+
Direction vectors for all nodes
|
|
235
|
+
step_sizes : ndarray
|
|
236
|
+
Step sizes for all nodes
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
-------
|
|
240
|
+
list of ndarray
|
|
241
|
+
Step vectors for all nodes
|
|
242
|
+
"""
|
|
243
|
+
steps = []
|
|
244
|
+
|
|
245
|
+
for i, direction in enumerate(directions):
|
|
246
|
+
# Normalize direction
|
|
247
|
+
direction_norm = norm(direction.flatten())
|
|
248
|
+
if direction_norm < 1e-10: # Avoid division by zero
|
|
249
|
+
steps.append(np.zeros_like(direction))
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
normalized_direction = direction / direction_norm
|
|
253
|
+
|
|
254
|
+
# Apply step size
|
|
255
|
+
step = normalized_direction * step_sizes[i]
|
|
256
|
+
|
|
257
|
+
# Apply maxstep constraint if needed
|
|
258
|
+
step_length = norm(step.flatten())
|
|
259
|
+
if step_length > self.maxstep:
|
|
260
|
+
step = step * (self.maxstep / step_length)
|
|
261
|
+
print(f"node {i}: Step constrained by maxstep={self.maxstep}")
|
|
262
|
+
|
|
263
|
+
steps.append(step)
|
|
264
|
+
|
|
265
|
+
return steps
|
|
266
|
+
|
|
267
|
+
def CG_NEB_calc(self, geometry_num_list, total_force_list, pre_total_velocity,
|
|
268
|
+
optimize_num, total_velocity, cos_list,
|
|
269
|
+
biased_energy_list, pre_biased_energy_list, pre_geom):
|
|
270
|
+
"""Calculate step using Conjugate Gradient for NEB.
|
|
271
|
+
|
|
272
|
+
Parameters:
|
|
273
|
+
----------
|
|
274
|
+
geometry_num_list : ndarray
|
|
275
|
+
Current geometry coordinates for all nodes
|
|
276
|
+
total_force_list : ndarray
|
|
277
|
+
Current forces for all nodes
|
|
278
|
+
pre_total_velocity : ndarray
|
|
279
|
+
Previous velocities for all nodes
|
|
280
|
+
optimize_num : int
|
|
281
|
+
Current optimization iteration number
|
|
282
|
+
total_velocity : ndarray
|
|
283
|
+
Current velocities for all nodes
|
|
284
|
+
cos_list : ndarray
|
|
285
|
+
Cosines between adjacent nodes
|
|
286
|
+
biased_energy_list : ndarray
|
|
287
|
+
Current energy for all nodes
|
|
288
|
+
pre_biased_energy_list : ndarray
|
|
289
|
+
Previous energy for all nodes
|
|
290
|
+
pre_geom : ndarray
|
|
291
|
+
Previous geometry coordinates for all nodes
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
-------
|
|
295
|
+
ndarray
|
|
296
|
+
Updated geometry coordinates for all nodes
|
|
297
|
+
"""
|
|
298
|
+
print(f"\n{'='*50}\nNEB-CG Iteration {self.iter}\n{'='*50}")
|
|
299
|
+
|
|
300
|
+
# Get number of nodes
|
|
301
|
+
num_nodes = len(geometry_num_list)
|
|
302
|
+
|
|
303
|
+
# Initialize data structures if first iteration
|
|
304
|
+
if self.Initialization:
|
|
305
|
+
self.initialize_data(num_nodes)
|
|
306
|
+
self.Initialization = False
|
|
307
|
+
print("First iteration - using steepest descent")
|
|
308
|
+
|
|
309
|
+
# Adjust step sizes based on energy changes (if not first iteration)
|
|
310
|
+
if not self.Initialization and self.iter > 0 and self.prev_energies[0] is not None:
|
|
311
|
+
self.adjust_step_sizes(biased_energy_list, self.prev_energies, total_force_list)
|
|
312
|
+
|
|
313
|
+
# Compute conjugate gradient directions for each node
|
|
314
|
+
cg_directions = []
|
|
315
|
+
for i, force in enumerate(total_force_list):
|
|
316
|
+
direction = self.compute_cg_direction(force, i)
|
|
317
|
+
cg_directions.append(direction)
|
|
318
|
+
|
|
319
|
+
# Determine step vectors from directions and step sizes
|
|
320
|
+
move_vectors = self.determine_step(cg_directions, self.node_step_sizes)
|
|
321
|
+
|
|
322
|
+
# Store current data for next iteration
|
|
323
|
+
for i in range(num_nodes):
|
|
324
|
+
self.prev_forces[i] = total_force_list[i].copy()
|
|
325
|
+
self.prev_directions[i] = cg_directions[i].copy()
|
|
326
|
+
self.prev_positions[i] = geometry_num_list[i].copy()
|
|
327
|
+
self.prev_energies[i] = biased_energy_list[i]
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
if self.TR_NEB:
|
|
331
|
+
move_vectors = self.TR_NEB.TR_calc(geometry_num_list, total_force_list, move_vectors,
|
|
332
|
+
biased_energy_list, pre_biased_energy_list, pre_geom)
|
|
333
|
+
|
|
334
|
+
# Update geometry using move vectors
|
|
335
|
+
new_geometry = []
|
|
336
|
+
for i, (geom, move) in enumerate(zip(geometry_num_list, move_vectors)):
|
|
337
|
+
new_geom = geom + move
|
|
338
|
+
new_geometry.append(new_geom)
|
|
339
|
+
|
|
340
|
+
# Convert to numpy array and apply unit conversion
|
|
341
|
+
new_geometry = np.array(new_geometry)
|
|
342
|
+
new_geometry *= self.bohr2angstroms
|
|
343
|
+
|
|
344
|
+
self.iter += 1
|
|
345
|
+
return new_geometry
|