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,625 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.linalg import solve, lstsq
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class KrylovDIIS:
|
|
7
|
+
"""
|
|
8
|
+
Implementation of Krylov-DIIS optimization method.
|
|
9
|
+
|
|
10
|
+
This method combines Krylov subspace techniques with DIIS extrapolation
|
|
11
|
+
for accelerated optimization, particularly effective for large-scale
|
|
12
|
+
quantum chemical calculations.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
# Krylov subspace parameters
|
|
17
|
+
self.krylov_dimension = 5 # Dimension of the Krylov subspace
|
|
18
|
+
self.krylov_restart = 10 # Number of steps before Krylov restart
|
|
19
|
+
self.orthogonalization = 'mgs' # Orthogonalization method ('mgs' or 'arnoldi')
|
|
20
|
+
|
|
21
|
+
# DIIS parameters
|
|
22
|
+
self.diis_max_points = 6 # Maximum number of DIIS points to use
|
|
23
|
+
self.diis_min_points = 3 # Minimum points before starting DIIS
|
|
24
|
+
self.diis_error_metric = 'grad' # Error metric ('grad', 'energy_change', 'combined')
|
|
25
|
+
self.diis_start_iter = 2 # Iteration to start DIIS
|
|
26
|
+
|
|
27
|
+
# Numerical parameters
|
|
28
|
+
self.diis_regularization = 1e-8 # Regularization for DIIS matrix
|
|
29
|
+
self.krylov_tolerance = 1e-10 # Tolerance for Krylov basis
|
|
30
|
+
self.error_threshold = 1.0 # Error threshold for including points
|
|
31
|
+
self.weight_factor = 0.9 # Weight factor for DIIS extrapolation
|
|
32
|
+
|
|
33
|
+
# Adaptation parameters
|
|
34
|
+
self.dynamic_subspace = True # Whether to adapt Krylov dimension
|
|
35
|
+
self.krylov_min_dim = 3 # Minimum Krylov dimension
|
|
36
|
+
self.krylov_max_dim = 10 # Maximum Krylov dimension
|
|
37
|
+
self.adapt_frequency = 5 # How often to adapt Krylov dimension
|
|
38
|
+
|
|
39
|
+
# Fallback and recovery
|
|
40
|
+
self.max_diis_failures = 2 # Maximum consecutive DIIS failures
|
|
41
|
+
self.stabilize_step = False # Whether to stabilize with SD step
|
|
42
|
+
self.recovery_steps = 2 # Number of steps in recovery mode
|
|
43
|
+
|
|
44
|
+
# History storage
|
|
45
|
+
self.geom_history = [] # Geometry history
|
|
46
|
+
self.grad_history = [] # Gradient history
|
|
47
|
+
self.energy_history = [] # Energy history
|
|
48
|
+
self.error_history = [] # Error vector history
|
|
49
|
+
self.krylov_basis = [] # Current Krylov basis
|
|
50
|
+
self.hess_proj = None # Projected Hessian in Krylov subspace
|
|
51
|
+
|
|
52
|
+
# Performance tracking
|
|
53
|
+
self.diis_failure_count = 0 # Count of DIIS failures
|
|
54
|
+
self.recovery_counter = 0 # Counter for recovery steps
|
|
55
|
+
self.iter = 0 # Iteration counter
|
|
56
|
+
|
|
57
|
+
def _update_histories(self, geometry, gradient, energy):
|
|
58
|
+
"""
|
|
59
|
+
Update the optimization histories with current data.
|
|
60
|
+
|
|
61
|
+
Parameters:
|
|
62
|
+
-----------
|
|
63
|
+
geometry : numpy.ndarray
|
|
64
|
+
Current geometry
|
|
65
|
+
gradient : numpy.ndarray
|
|
66
|
+
Current gradient
|
|
67
|
+
energy : float
|
|
68
|
+
Current energy value
|
|
69
|
+
"""
|
|
70
|
+
# Add new data to histories
|
|
71
|
+
self.geom_history.append(geometry.copy())
|
|
72
|
+
self.grad_history.append(gradient.copy())
|
|
73
|
+
self.energy_history.append(energy)
|
|
74
|
+
|
|
75
|
+
# Calculate error vector based on selected metric
|
|
76
|
+
error_vec = self._calculate_error_vector(gradient)
|
|
77
|
+
self.error_history.append(error_vec)
|
|
78
|
+
|
|
79
|
+
# Limit history size to maximum DIIS points
|
|
80
|
+
if len(self.geom_history) > self.diis_max_points:
|
|
81
|
+
self.geom_history.pop(0)
|
|
82
|
+
self.grad_history.pop(0)
|
|
83
|
+
self.energy_history.pop(0)
|
|
84
|
+
self.error_history.pop(0)
|
|
85
|
+
|
|
86
|
+
def _calculate_error_vector(self, gradient):
|
|
87
|
+
"""
|
|
88
|
+
Calculate the error vector for DIIS extrapolation.
|
|
89
|
+
|
|
90
|
+
Parameters:
|
|
91
|
+
-----------
|
|
92
|
+
gradient : numpy.ndarray
|
|
93
|
+
Current gradient
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
--------
|
|
97
|
+
numpy.ndarray
|
|
98
|
+
Error vector
|
|
99
|
+
"""
|
|
100
|
+
if self.diis_error_metric == 'grad':
|
|
101
|
+
# Use gradient as error vector
|
|
102
|
+
return gradient.copy()
|
|
103
|
+
|
|
104
|
+
elif self.diis_error_metric == 'energy_change' and len(self.energy_history) > 0:
|
|
105
|
+
# Use energy change combined with gradient
|
|
106
|
+
grad_norm = np.linalg.norm(gradient)
|
|
107
|
+
if grad_norm < 1e-10:
|
|
108
|
+
return gradient.copy()
|
|
109
|
+
|
|
110
|
+
# Scale by energy change if possible
|
|
111
|
+
if len(self.energy_history) > 0:
|
|
112
|
+
prev_energy = self.energy_history[-1]
|
|
113
|
+
energy_diff = abs(self.energy_history[-1] - prev_energy)
|
|
114
|
+
scale_factor = np.sqrt(1.0 + energy_diff)
|
|
115
|
+
return gradient * scale_factor
|
|
116
|
+
else:
|
|
117
|
+
return gradient.copy()
|
|
118
|
+
|
|
119
|
+
elif self.diis_error_metric == 'combined' and len(self.grad_history) > 0:
|
|
120
|
+
# Combine current gradient with gradient difference
|
|
121
|
+
prev_grad = self.grad_history[-1]
|
|
122
|
+
grad_diff = gradient - prev_grad
|
|
123
|
+
return gradient + 0.5 * grad_diff
|
|
124
|
+
|
|
125
|
+
else:
|
|
126
|
+
# Default to gradient
|
|
127
|
+
return gradient.copy()
|
|
128
|
+
|
|
129
|
+
def _build_krylov_subspace(self, gradient):
|
|
130
|
+
"""
|
|
131
|
+
Build or update the Krylov subspace.
|
|
132
|
+
|
|
133
|
+
Parameters:
|
|
134
|
+
-----------
|
|
135
|
+
gradient : numpy.ndarray
|
|
136
|
+
Current gradient
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
--------
|
|
140
|
+
bool
|
|
141
|
+
Whether the Krylov basis was successfully updated
|
|
142
|
+
"""
|
|
143
|
+
# Check if we need to restart
|
|
144
|
+
restart = (self.iter % self.krylov_restart == 0) or not self.krylov_basis
|
|
145
|
+
|
|
146
|
+
if restart:
|
|
147
|
+
# Start a new Krylov basis with the current gradient
|
|
148
|
+
self.krylov_basis = []
|
|
149
|
+
|
|
150
|
+
# Normalize gradient to start the basis
|
|
151
|
+
grad_norm = np.linalg.norm(gradient)
|
|
152
|
+
if grad_norm < self.krylov_tolerance:
|
|
153
|
+
# Gradient too small, use random vector
|
|
154
|
+
warnings.warn("Gradient norm too small for Krylov basis, using random vector")
|
|
155
|
+
v = np.random.randn(*gradient.shape)
|
|
156
|
+
v = v / np.linalg.norm(v)
|
|
157
|
+
else:
|
|
158
|
+
# Use normalized gradient
|
|
159
|
+
v = gradient / grad_norm
|
|
160
|
+
|
|
161
|
+
# Start with normalized gradient
|
|
162
|
+
self.krylov_basis = [v]
|
|
163
|
+
self.hess_proj = None
|
|
164
|
+
|
|
165
|
+
# Determine current Krylov dimension (may be adaptive)
|
|
166
|
+
if self.dynamic_subspace and self.iter % self.adapt_frequency == 0:
|
|
167
|
+
# Adapt dimension based on convergence or problem size
|
|
168
|
+
grad_norm = np.linalg.norm(gradient)
|
|
169
|
+
if len(self.grad_history) > 2:
|
|
170
|
+
prev_grad_norm = np.linalg.norm(self.grad_history[-2])
|
|
171
|
+
# If convergence is slow, increase dimension
|
|
172
|
+
if grad_norm > 0.7 * prev_grad_norm:
|
|
173
|
+
self.krylov_dimension = min(self.krylov_dimension + 1, self.krylov_max_dim)
|
|
174
|
+
# If convergence is fast, decrease dimension
|
|
175
|
+
elif grad_norm < 0.3 * prev_grad_norm:
|
|
176
|
+
self.krylov_dimension = max(self.krylov_dimension - 1, self.krylov_min_dim)
|
|
177
|
+
|
|
178
|
+
# Expand the Krylov basis if needed
|
|
179
|
+
while len(self.krylov_basis) < self.krylov_dimension:
|
|
180
|
+
try:
|
|
181
|
+
# Generate next Krylov vector (approximately Hv)
|
|
182
|
+
v_prev = self.krylov_basis[-1]
|
|
183
|
+
|
|
184
|
+
# Approximate Hessian-vector product using finite differences
|
|
185
|
+
# If we have enough history
|
|
186
|
+
if len(self.grad_history) >= 2 and len(self.geom_history) >= 2:
|
|
187
|
+
# Get finite difference approximation of Hessian-vector product
|
|
188
|
+
g_diff = self.grad_history[-1] - self.grad_history[-2]
|
|
189
|
+
x_diff = self.geom_history[-1] - self.geom_history[-2]
|
|
190
|
+
|
|
191
|
+
# Compute direction in x_diff parallel to v_prev
|
|
192
|
+
v_comp = np.dot(x_diff.flatten(), v_prev.flatten())
|
|
193
|
+
|
|
194
|
+
# If sufficient component in this direction
|
|
195
|
+
if abs(v_comp) > 1e-10:
|
|
196
|
+
# Approximate Hessian-vector product
|
|
197
|
+
Hv = g_diff * (np.dot(v_prev.flatten(), v_prev.flatten()) / v_comp)
|
|
198
|
+
else:
|
|
199
|
+
# Fallback: use gradient difference directly
|
|
200
|
+
Hv = g_diff
|
|
201
|
+
else:
|
|
202
|
+
# Not enough history, use gradient as approximation
|
|
203
|
+
Hv = gradient
|
|
204
|
+
|
|
205
|
+
# Orthogonalize against existing basis
|
|
206
|
+
if self.orthogonalization == 'mgs':
|
|
207
|
+
# Modified Gram-Schmidt orthogonalization
|
|
208
|
+
v_next = Hv.copy()
|
|
209
|
+
for v in self.krylov_basis:
|
|
210
|
+
proj = np.dot(v.flatten(), v_next.flatten())
|
|
211
|
+
v_next = v_next - proj * v
|
|
212
|
+
else:
|
|
213
|
+
# Simple Arnoldi-like orthogonalization
|
|
214
|
+
v_next = Hv.copy()
|
|
215
|
+
for v in self.krylov_basis:
|
|
216
|
+
v_next = v_next - np.dot(v_next.flatten(), v.flatten()) * v
|
|
217
|
+
|
|
218
|
+
# Normalize the new vector
|
|
219
|
+
v_next_norm = np.linalg.norm(v_next)
|
|
220
|
+
|
|
221
|
+
# Check if new vector is sufficiently linearly independent
|
|
222
|
+
if v_next_norm < self.krylov_tolerance:
|
|
223
|
+
# Basis cannot be expanded further
|
|
224
|
+
print(f"Krylov basis saturated at dimension {len(self.krylov_basis)}")
|
|
225
|
+
break
|
|
226
|
+
|
|
227
|
+
# Add normalized vector to basis
|
|
228
|
+
v_next = v_next / v_next_norm
|
|
229
|
+
self.krylov_basis.append(v_next)
|
|
230
|
+
|
|
231
|
+
# Update the projected Hessian
|
|
232
|
+
self._update_projected_hessian(Hv, v_next, len(self.krylov_basis)-1)
|
|
233
|
+
|
|
234
|
+
except Exception as e:
|
|
235
|
+
print(f"Error expanding Krylov basis: {str(e)}")
|
|
236
|
+
# Return current basis
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
return len(self.krylov_basis) > 1
|
|
240
|
+
|
|
241
|
+
def _update_projected_hessian(self, Hv, v_next, idx):
|
|
242
|
+
"""
|
|
243
|
+
Update the projected Hessian in the Krylov subspace.
|
|
244
|
+
|
|
245
|
+
Parameters:
|
|
246
|
+
-----------
|
|
247
|
+
Hv : numpy.ndarray
|
|
248
|
+
Hessian-vector product
|
|
249
|
+
v_next : numpy.ndarray
|
|
250
|
+
Next basis vector
|
|
251
|
+
idx : int
|
|
252
|
+
Index of the new vector
|
|
253
|
+
"""
|
|
254
|
+
k = len(self.krylov_basis)
|
|
255
|
+
|
|
256
|
+
# Initialize or expand projected Hessian
|
|
257
|
+
if self.hess_proj is None:
|
|
258
|
+
self.hess_proj = np.zeros((k, k))
|
|
259
|
+
elif self.hess_proj.shape[0] < k:
|
|
260
|
+
# Expand Hessian matrix
|
|
261
|
+
new_hess = np.zeros((k, k))
|
|
262
|
+
old_size = self.hess_proj.shape[0]
|
|
263
|
+
new_hess[:old_size, :old_size] = self.hess_proj
|
|
264
|
+
self.hess_proj = new_hess
|
|
265
|
+
|
|
266
|
+
# Update the Hessian elements for the new vector
|
|
267
|
+
for i in range(k):
|
|
268
|
+
# H_ij = v_i^T * H * v_j
|
|
269
|
+
if i < idx:
|
|
270
|
+
self.hess_proj[i, idx] = np.dot(self.krylov_basis[i].flatten(), Hv.flatten())
|
|
271
|
+
self.hess_proj[idx, i] = self.hess_proj[i, idx] # Symmetry
|
|
272
|
+
elif i == idx:
|
|
273
|
+
self.hess_proj[idx, idx] = np.dot(v_next.flatten(), Hv.flatten())
|
|
274
|
+
|
|
275
|
+
def _solve_krylov_system(self, gradient):
|
|
276
|
+
"""
|
|
277
|
+
Solve the optimization problem in the Krylov subspace.
|
|
278
|
+
|
|
279
|
+
Parameters:
|
|
280
|
+
-----------
|
|
281
|
+
gradient : numpy.ndarray
|
|
282
|
+
Current gradient
|
|
283
|
+
|
|
284
|
+
Returns:
|
|
285
|
+
--------
|
|
286
|
+
tuple
|
|
287
|
+
(step, success)
|
|
288
|
+
"""
|
|
289
|
+
k = len(self.krylov_basis)
|
|
290
|
+
|
|
291
|
+
if k < 2:
|
|
292
|
+
# Not enough basis vectors
|
|
293
|
+
return None, False
|
|
294
|
+
|
|
295
|
+
# Project gradient into Krylov subspace
|
|
296
|
+
g_proj = np.zeros(k)
|
|
297
|
+
for i in range(k):
|
|
298
|
+
g_proj[i] = np.dot(self.krylov_basis[i].flatten(), gradient.flatten())
|
|
299
|
+
|
|
300
|
+
# Check if projected Hessian is available
|
|
301
|
+
if self.hess_proj is None or self.hess_proj.shape[0] < k:
|
|
302
|
+
return None, False
|
|
303
|
+
|
|
304
|
+
try:
|
|
305
|
+
# Try to solve the system (H_proj * alpha = -g_proj)
|
|
306
|
+
# Add regularization for stability
|
|
307
|
+
H_reg = self.hess_proj + self.diis_regularization * np.eye(k)
|
|
308
|
+
alpha = solve(H_reg, -g_proj)
|
|
309
|
+
|
|
310
|
+
# Construct the step in the original space
|
|
311
|
+
step = np.zeros_like(gradient)
|
|
312
|
+
for i in range(k):
|
|
313
|
+
step += alpha[i] * self.krylov_basis[i]
|
|
314
|
+
|
|
315
|
+
return step, True
|
|
316
|
+
|
|
317
|
+
except Exception as e:
|
|
318
|
+
print(f"Failed to solve Krylov system: {str(e)}")
|
|
319
|
+
return None, False
|
|
320
|
+
|
|
321
|
+
def _solve_diis_system(self):
|
|
322
|
+
"""
|
|
323
|
+
Solve the DIIS extrapolation problem.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
--------
|
|
327
|
+
tuple
|
|
328
|
+
(diis_geometry, coefficients, success)
|
|
329
|
+
"""
|
|
330
|
+
n_points = len(self.geom_history)
|
|
331
|
+
|
|
332
|
+
if n_points < self.diis_min_points:
|
|
333
|
+
# Not enough points for DIIS
|
|
334
|
+
return None, None, False
|
|
335
|
+
|
|
336
|
+
# Construct the DIIS B matrix
|
|
337
|
+
B = np.zeros((n_points + 1, n_points + 1))
|
|
338
|
+
|
|
339
|
+
# Fill B matrix with error vector dot products
|
|
340
|
+
for i in range(n_points):
|
|
341
|
+
for j in range(n_points):
|
|
342
|
+
B[i, j] = np.dot(self.error_history[i].flatten(),
|
|
343
|
+
self.error_history[j].flatten())
|
|
344
|
+
|
|
345
|
+
# Add regularization to diagonal for numerical stability
|
|
346
|
+
np.fill_diagonal(B[:n_points, :n_points],
|
|
347
|
+
np.diag(B[:n_points, :n_points]) + self.diis_regularization)
|
|
348
|
+
|
|
349
|
+
# Add Lagrange multiplier constraints
|
|
350
|
+
B[n_points, :n_points] = 1.0
|
|
351
|
+
B[:n_points, n_points] = 1.0
|
|
352
|
+
B[n_points, n_points] = 0.0
|
|
353
|
+
|
|
354
|
+
# Right-hand side vector with constraint
|
|
355
|
+
rhs = np.zeros(n_points + 1)
|
|
356
|
+
rhs[n_points] = 1.0
|
|
357
|
+
|
|
358
|
+
try:
|
|
359
|
+
# Try to solve the DIIS equations
|
|
360
|
+
coeffs = solve(B, rhs)[:n_points]
|
|
361
|
+
|
|
362
|
+
# Check for reasonable coefficients
|
|
363
|
+
if np.any(np.isnan(coeffs)) or np.max(np.abs(coeffs)) > 10.0:
|
|
364
|
+
raise ValueError("DIIS produced extreme coefficients")
|
|
365
|
+
|
|
366
|
+
# Calculate the extrapolated geometry
|
|
367
|
+
diis_geom = np.zeros_like(self.geom_history[0])
|
|
368
|
+
for i in range(n_points):
|
|
369
|
+
diis_geom += coeffs[i] * self.geom_history[i]
|
|
370
|
+
|
|
371
|
+
return diis_geom, coeffs, True
|
|
372
|
+
|
|
373
|
+
except Exception as e:
|
|
374
|
+
print(f"DIIS system solve failed: {str(e)}")
|
|
375
|
+
|
|
376
|
+
try:
|
|
377
|
+
# Fallback: try least squares with non-negative constraints
|
|
378
|
+
coeffs = np.zeros(n_points)
|
|
379
|
+
coeffs[-1] = 1.0 # Start with most recent point
|
|
380
|
+
|
|
381
|
+
return self.geom_history[-1].copy(), coeffs, False
|
|
382
|
+
|
|
383
|
+
except:
|
|
384
|
+
# Ultimate fallback: use most recent point
|
|
385
|
+
coeffs = np.zeros(n_points)
|
|
386
|
+
coeffs[-1] = 1.0
|
|
387
|
+
return self.geom_history[-1].copy(), coeffs, False
|
|
388
|
+
|
|
389
|
+
def _blend_steps(self, krylov_step, diis_step, original_step, gradient):
|
|
390
|
+
"""
|
|
391
|
+
Blend different steps based on their quality.
|
|
392
|
+
|
|
393
|
+
Parameters:
|
|
394
|
+
-----------
|
|
395
|
+
krylov_step : numpy.ndarray or None
|
|
396
|
+
Step from Krylov subspace optimization
|
|
397
|
+
diis_step : numpy.ndarray or None
|
|
398
|
+
Step from DIIS extrapolation
|
|
399
|
+
original_step : numpy.ndarray
|
|
400
|
+
Original optimizer step
|
|
401
|
+
gradient : numpy.ndarray
|
|
402
|
+
Current gradient
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
--------
|
|
406
|
+
numpy.ndarray
|
|
407
|
+
Blended step
|
|
408
|
+
"""
|
|
409
|
+
# Use negative gradient direction for evaluating alignment
|
|
410
|
+
grad_norm = np.linalg.norm(gradient)
|
|
411
|
+
if grad_norm > 1e-10:
|
|
412
|
+
neg_grad_dir = -gradient / grad_norm
|
|
413
|
+
else:
|
|
414
|
+
# If gradient is nearly zero, any direction is fine
|
|
415
|
+
return original_step
|
|
416
|
+
|
|
417
|
+
# Start with original step
|
|
418
|
+
blend_weights = {"original": 1.0, "krylov": 0.0, "diis": 0.0}
|
|
419
|
+
|
|
420
|
+
# Evaluate Krylov step if available
|
|
421
|
+
if krylov_step is not None:
|
|
422
|
+
krylov_norm = np.linalg.norm(krylov_step)
|
|
423
|
+
if krylov_norm > 1e-10:
|
|
424
|
+
# Check alignment with negative gradient
|
|
425
|
+
krylov_align = np.dot(krylov_step.flatten(), neg_grad_dir.flatten()) / krylov_norm
|
|
426
|
+
|
|
427
|
+
# If reasonably aligned with descent direction
|
|
428
|
+
if krylov_align > 0.1:
|
|
429
|
+
# Allocate weight to Krylov step
|
|
430
|
+
krylov_weight = min(0.7, max(0.3, krylov_align))
|
|
431
|
+
blend_weights["krylov"] = krylov_weight
|
|
432
|
+
blend_weights["original"] -= krylov_weight * 0.7 # Reduce original weight
|
|
433
|
+
|
|
434
|
+
# Evaluate DIIS step if available
|
|
435
|
+
if diis_step is not None:
|
|
436
|
+
diis_norm = np.linalg.norm(diis_step)
|
|
437
|
+
if diis_norm > 1e-10:
|
|
438
|
+
# Calculate DIIS step (extrapolated geometry - current geometry)
|
|
439
|
+
diis_step_vec = diis_step - self.geom_history[-1]
|
|
440
|
+
diis_step_norm = np.linalg.norm(diis_step_vec)
|
|
441
|
+
|
|
442
|
+
if diis_step_norm > 1e-10:
|
|
443
|
+
# Check alignment with negative gradient
|
|
444
|
+
diis_align = np.dot(diis_step_vec.flatten(), neg_grad_dir.flatten()) / diis_step_norm
|
|
445
|
+
|
|
446
|
+
# If reasonably aligned with descent direction
|
|
447
|
+
if diis_align > 0.0:
|
|
448
|
+
# Allocate weight to DIIS step based on accuracy history
|
|
449
|
+
diis_weight = self.weight_factor * min(0.8, max(0.2, diis_align))
|
|
450
|
+
blend_weights["diis"] = diis_weight
|
|
451
|
+
# Reduce other weights proportionally
|
|
452
|
+
total_other = blend_weights["original"] + blend_weights["krylov"]
|
|
453
|
+
if total_other > 0:
|
|
454
|
+
factor = (1.0 - diis_weight) / total_other
|
|
455
|
+
blend_weights["original"] *= factor
|
|
456
|
+
blend_weights["krylov"] *= factor
|
|
457
|
+
|
|
458
|
+
# Normalize weights (should sum to 1.0)
|
|
459
|
+
total_weight = sum(blend_weights.values())
|
|
460
|
+
if abs(total_weight - 1.0) > 1e-10:
|
|
461
|
+
for k in blend_weights:
|
|
462
|
+
blend_weights[k] /= total_weight
|
|
463
|
+
|
|
464
|
+
print(f"Blend weights: Original={blend_weights['original']:.3f}, "
|
|
465
|
+
f"Krylov={blend_weights['krylov']:.3f}, DIIS={blend_weights['diis']:.3f}")
|
|
466
|
+
|
|
467
|
+
# Construct the blended step
|
|
468
|
+
blended_step = np.zeros_like(original_step)
|
|
469
|
+
|
|
470
|
+
# Add original step contribution
|
|
471
|
+
blended_step += blend_weights["original"] * original_step
|
|
472
|
+
|
|
473
|
+
# Add Krylov step contribution
|
|
474
|
+
if krylov_step is not None and blend_weights["krylov"] > 0:
|
|
475
|
+
# Scale to similar magnitude as original step
|
|
476
|
+
orig_norm = np.linalg.norm(original_step)
|
|
477
|
+
krylov_norm = np.linalg.norm(krylov_step)
|
|
478
|
+
|
|
479
|
+
if krylov_norm > 1e-10 and orig_norm > 1e-10:
|
|
480
|
+
# Scale Krylov step if it's much larger/smaller
|
|
481
|
+
if krylov_norm > 2.0 * orig_norm:
|
|
482
|
+
scale = 2.0 * orig_norm / krylov_norm
|
|
483
|
+
krylov_step = krylov_step * scale
|
|
484
|
+
elif krylov_norm < 0.5 * orig_norm:
|
|
485
|
+
scale = 0.5 * orig_norm / krylov_norm
|
|
486
|
+
krylov_step = krylov_step * scale
|
|
487
|
+
|
|
488
|
+
blended_step += blend_weights["krylov"] * krylov_step
|
|
489
|
+
|
|
490
|
+
# Add DIIS step contribution
|
|
491
|
+
if diis_step is not None and blend_weights["diis"] > 0:
|
|
492
|
+
# Convert DIIS geometry to step vector
|
|
493
|
+
diis_step_vec = diis_step - self.geom_history[-1]
|
|
494
|
+
|
|
495
|
+
# Scale to similar magnitude as original step
|
|
496
|
+
orig_norm = np.linalg.norm(original_step)
|
|
497
|
+
diis_norm = np.linalg.norm(diis_step_vec)
|
|
498
|
+
|
|
499
|
+
if diis_norm > 1e-10 and orig_norm > 1e-10:
|
|
500
|
+
# Scale DIIS step if it's much larger/smaller
|
|
501
|
+
if diis_norm > 2.0 * orig_norm:
|
|
502
|
+
scale = 2.0 * orig_norm / diis_norm
|
|
503
|
+
diis_step_vec = diis_step_vec * scale
|
|
504
|
+
elif diis_norm < 0.5 * orig_norm:
|
|
505
|
+
scale = 0.5 * orig_norm / diis_norm
|
|
506
|
+
diis_step_vec = diis_step_vec * scale
|
|
507
|
+
|
|
508
|
+
blended_step += blend_weights["diis"] * diis_step_vec.reshape(original_step.shape)
|
|
509
|
+
|
|
510
|
+
# Final safety check
|
|
511
|
+
blended_norm = np.linalg.norm(blended_step)
|
|
512
|
+
orig_norm = np.linalg.norm(original_step)
|
|
513
|
+
|
|
514
|
+
if blended_norm > 3.0 * orig_norm:
|
|
515
|
+
# Cap the step size for safety
|
|
516
|
+
print(f"Capping step size: {blended_norm:.4f} → {3.0 * orig_norm:.4f}")
|
|
517
|
+
blended_step = blended_step * (3.0 * orig_norm / blended_norm)
|
|
518
|
+
|
|
519
|
+
return blended_step
|
|
520
|
+
|
|
521
|
+
def run(self, geom_num_list, energy, gradient, original_move_vector):
|
|
522
|
+
"""
|
|
523
|
+
Run Krylov-DIIS optimization step.
|
|
524
|
+
|
|
525
|
+
Parameters:
|
|
526
|
+
-----------
|
|
527
|
+
geom_num_list : numpy.ndarray
|
|
528
|
+
Current geometry
|
|
529
|
+
energy : float
|
|
530
|
+
Current energy value
|
|
531
|
+
gradient : numpy.ndarray
|
|
532
|
+
Current gradient
|
|
533
|
+
original_move_vector : numpy.ndarray
|
|
534
|
+
Original optimization step
|
|
535
|
+
|
|
536
|
+
Returns:
|
|
537
|
+
--------
|
|
538
|
+
numpy.ndarray
|
|
539
|
+
Optimized step vector
|
|
540
|
+
"""
|
|
541
|
+
print("Krylov-DIIS method")
|
|
542
|
+
|
|
543
|
+
# Update histories
|
|
544
|
+
self._update_histories(geom_num_list, gradient, energy)
|
|
545
|
+
|
|
546
|
+
# Skip advanced methods in very early iterations
|
|
547
|
+
if self.iter < self.diis_start_iter:
|
|
548
|
+
print(f"Building history (iteration {self.iter+1}), using original step")
|
|
549
|
+
self.iter += 1
|
|
550
|
+
return original_move_vector
|
|
551
|
+
|
|
552
|
+
# Skip if in recovery mode
|
|
553
|
+
if self.recovery_counter > 0:
|
|
554
|
+
print(f"In recovery mode ({self.recovery_counter} steps remaining)")
|
|
555
|
+
self.recovery_counter -= 1
|
|
556
|
+
self.iter += 1
|
|
557
|
+
return original_move_vector
|
|
558
|
+
|
|
559
|
+
# Build/update Krylov subspace
|
|
560
|
+
krylov_success = self._build_krylov_subspace(gradient)
|
|
561
|
+
print(f"Krylov basis dimension: {len(self.krylov_basis)}")
|
|
562
|
+
|
|
563
|
+
# Attempt Krylov subspace optimization
|
|
564
|
+
krylov_step = None
|
|
565
|
+
if krylov_success:
|
|
566
|
+
krylov_step, krylov_solved = self._solve_krylov_system(gradient)
|
|
567
|
+
if krylov_solved:
|
|
568
|
+
print("Krylov subspace optimization successful")
|
|
569
|
+
else:
|
|
570
|
+
print("Krylov subspace optimization failed")
|
|
571
|
+
|
|
572
|
+
# Attempt DIIS extrapolation if we have enough history
|
|
573
|
+
diis_geom = None
|
|
574
|
+
if len(self.geom_history) >= self.diis_min_points and self.diis_failure_count < self.max_diis_failures:
|
|
575
|
+
diis_geom, diis_coeffs, diis_success = self._solve_diis_system()
|
|
576
|
+
|
|
577
|
+
if diis_success:
|
|
578
|
+
print("DIIS extrapolation successful")
|
|
579
|
+
print("DIIS coefficients:", ", ".join(f"{c:.4f}" for c in diis_coeffs))
|
|
580
|
+
# Reset failure counter on success
|
|
581
|
+
self.diis_failure_count = max(0, self.diis_failure_count - 1)
|
|
582
|
+
else:
|
|
583
|
+
print("DIIS extrapolation failed")
|
|
584
|
+
self.diis_failure_count += 1
|
|
585
|
+
|
|
586
|
+
# If too many failures, enter recovery mode
|
|
587
|
+
if self.diis_failure_count >= self.max_diis_failures:
|
|
588
|
+
print(f"Too many DIIS failures ({self.diis_failure_count}), entering recovery mode")
|
|
589
|
+
self.recovery_counter = self.recovery_steps
|
|
590
|
+
|
|
591
|
+
# Blend the steps
|
|
592
|
+
blended_step = self._blend_steps(krylov_step, diis_geom, original_move_vector, gradient)
|
|
593
|
+
|
|
594
|
+
# Add a small steepest descent component for stability if requested
|
|
595
|
+
if self.stabilize_step:
|
|
596
|
+
grad_norm = np.linalg.norm(gradient)
|
|
597
|
+
if grad_norm > 1e-10:
|
|
598
|
+
sd_step = -0.05 * gradient / grad_norm
|
|
599
|
+
step_norm = np.linalg.norm(blended_step)
|
|
600
|
+
if step_norm > 1e-10:
|
|
601
|
+
# Scale SD step relative to blended step
|
|
602
|
+
sd_step = sd_step * (0.1 * step_norm / np.linalg.norm(sd_step))
|
|
603
|
+
blended_step += sd_step
|
|
604
|
+
print("Added stabilizing SD component")
|
|
605
|
+
|
|
606
|
+
# Final checks
|
|
607
|
+
if np.any(np.isnan(blended_step)) or np.any(np.isinf(blended_step)):
|
|
608
|
+
print("Warning: Numerical issues in step, using original step")
|
|
609
|
+
move_vector = original_move_vector
|
|
610
|
+
else:
|
|
611
|
+
move_vector = blended_step
|
|
612
|
+
|
|
613
|
+
# Make sure step isn't too small
|
|
614
|
+
if np.linalg.norm(move_vector) < 1e-10:
|
|
615
|
+
print("Warning: Step too small, using scaled original step")
|
|
616
|
+
move_vector = original_move_vector
|
|
617
|
+
|
|
618
|
+
# If original also too small, use scaled negative gradient
|
|
619
|
+
if np.linalg.norm(move_vector) < 1e-10:
|
|
620
|
+
grad_norm = np.linalg.norm(gradient)
|
|
621
|
+
if grad_norm > 1e-10:
|
|
622
|
+
move_vector = -0.1 * gradient / grad_norm
|
|
623
|
+
|
|
624
|
+
self.iter += 1
|
|
625
|
+
return move_vector
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import copy
|
|
3
|
+
|
|
4
|
+
class LARS:
|
|
5
|
+
def __init__(self, beta=0.6, **config):
|
|
6
|
+
# ref. arXiv:1708.03888
|
|
7
|
+
# https://gist.github.com/redknightlois/c4023d393eb8f92bb44b2ab582d7ec20
|
|
8
|
+
self.beta = beta
|
|
9
|
+
self.config = config
|
|
10
|
+
self.iter = 0
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
def run(self, geom_num_list, B_g, pre_B_g, pre_geom, B_e, pre_B_e, pre_move_vector, initial_geom_num_list, g, pre_g, tmp_move_vector):
|
|
14
|
+
print("LARS")
|
|
15
|
+
weight = np.clip(np.linalg.norm(geom_num_list), 0, 10)
|
|
16
|
+
|
|
17
|
+
scaled_lr = (weight / (np.linalg.norm(tmp_move_vector) + weight * self.beta))
|
|
18
|
+
|
|
19
|
+
self.iter += 1
|
|
20
|
+
print("scaled learning rate: ", scaled_lr)
|
|
21
|
+
return scaled_lr#Bohr
|