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,364 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.linalg import cholesky, cho_solve
|
|
3
|
+
from scipy.optimize import minimize
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GaussianProcessRegression:
|
|
7
|
+
"""Scratch implementation of Gaussian Process Regression"""
|
|
8
|
+
|
|
9
|
+
def __init__(self, kernel='rbf', length_scale=1.0, amplitude=1.0, noise=1e-6):
|
|
10
|
+
# Kernel parameters
|
|
11
|
+
self.kernel_type = kernel
|
|
12
|
+
self.length_scale = length_scale
|
|
13
|
+
self.amplitude = amplitude
|
|
14
|
+
self.noise = noise
|
|
15
|
+
|
|
16
|
+
# Model state
|
|
17
|
+
self.X_train = None
|
|
18
|
+
self.y_train = None
|
|
19
|
+
self.L = None
|
|
20
|
+
self.alpha = None
|
|
21
|
+
|
|
22
|
+
def kernel(self, X1, X2=None):
|
|
23
|
+
"""Compute kernel matrix between X1 and X2"""
|
|
24
|
+
if X2 is None:
|
|
25
|
+
X2 = X1
|
|
26
|
+
|
|
27
|
+
# RBF/Squared exponential kernel
|
|
28
|
+
X1_norm = np.sum(X1**2, axis=1).reshape(-1, 1)
|
|
29
|
+
X2_norm = np.sum(X2**2, axis=1).reshape(1, -1)
|
|
30
|
+
K = X1_norm + X2_norm - 2 * np.dot(X1, X2.T)
|
|
31
|
+
K = self.amplitude**2 * np.exp(-K / (2 * self.length_scale**2))
|
|
32
|
+
return K
|
|
33
|
+
|
|
34
|
+
def _nll_fn(self, theta):
|
|
35
|
+
"""Negative log-likelihood for hyperparameter optimization"""
|
|
36
|
+
# Extract hyperparameters (in log space)
|
|
37
|
+
length_scale, amplitude, noise = np.exp(theta)
|
|
38
|
+
|
|
39
|
+
# Store original parameters
|
|
40
|
+
old_ls, old_amp, old_noise = self.length_scale, self.amplitude, self.noise
|
|
41
|
+
self.length_scale, self.amplitude, self.noise = length_scale, amplitude, noise
|
|
42
|
+
|
|
43
|
+
# Compute kernel with current hyperparameters
|
|
44
|
+
K = self.kernel(self.X_train)
|
|
45
|
+
K_noisy = K + np.eye(len(self.X_train)) * noise
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
# Compute log-likelihood
|
|
49
|
+
L = cholesky(K_noisy, lower=True)
|
|
50
|
+
alpha = cho_solve((L, True), self.y_train)
|
|
51
|
+
|
|
52
|
+
log_det = 2 * np.sum(np.log(np.diag(L)))
|
|
53
|
+
data_fit = np.dot(self.y_train.T, alpha)
|
|
54
|
+
nll = 0.5 * (data_fit + log_det + len(self.X_train) * np.log(2 * np.pi))
|
|
55
|
+
except np.linalg.LinAlgError:
|
|
56
|
+
nll = 1e10
|
|
57
|
+
|
|
58
|
+
# Restore original parameters
|
|
59
|
+
self.length_scale, self.amplitude, self.noise = old_ls, old_amp, old_noise
|
|
60
|
+
return nll
|
|
61
|
+
|
|
62
|
+
def optimize_parameters(self):
|
|
63
|
+
"""Optimize hyperparameters using maximum likelihood"""
|
|
64
|
+
if len(self.X_train) < 5:
|
|
65
|
+
return False
|
|
66
|
+
|
|
67
|
+
# Initial guess in log space
|
|
68
|
+
theta_init = [np.log(self.length_scale), np.log(self.amplitude), np.log(self.noise)]
|
|
69
|
+
bounds = [(np.log(0.01), np.log(10.0)), (np.log(0.1), np.log(10.0)), (np.log(1e-10), np.log(1e-2))]
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
res = minimize(self._nll_fn, theta_init, method='L-BFGS-B', bounds=bounds)
|
|
73
|
+
if res.success:
|
|
74
|
+
self.length_scale, self.amplitude, self.noise = np.exp(res.x)
|
|
75
|
+
print(f"Optimized hyperparameters: length_scale={self.length_scale:.4f}, "
|
|
76
|
+
f"amplitude={self.amplitude:.4f}, noise={self.noise:.6f}")
|
|
77
|
+
return True
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"Hyperparameter optimization failed: {str(e)}")
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
def fit(self, X, y, optimize=True):
|
|
83
|
+
"""Fit GP model to training data"""
|
|
84
|
+
self.X_train = X.copy()
|
|
85
|
+
self.y_train = y.copy()
|
|
86
|
+
|
|
87
|
+
# Optimize hyperparameters if requested
|
|
88
|
+
if optimize and len(X) >= 5:
|
|
89
|
+
self.optimize_parameters()
|
|
90
|
+
|
|
91
|
+
# Compute kernel matrix
|
|
92
|
+
K = self.kernel(X)
|
|
93
|
+
K_noisy = K + np.eye(len(X)) * self.noise
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
self.L = cholesky(K_noisy, lower=True)
|
|
97
|
+
self.alpha = cho_solve((self.L, True), y)
|
|
98
|
+
except np.linalg.LinAlgError:
|
|
99
|
+
# Add more regularization if Cholesky fails
|
|
100
|
+
K_noisy = K + np.eye(len(X)) * (self.noise + 1e-6)
|
|
101
|
+
self.alpha = np.linalg.solve(K_noisy, y)
|
|
102
|
+
self.L = None
|
|
103
|
+
|
|
104
|
+
return self
|
|
105
|
+
|
|
106
|
+
def predict(self, X, return_std=False):
|
|
107
|
+
"""Make predictions at query points X"""
|
|
108
|
+
if self.X_train is None or self.alpha is None:
|
|
109
|
+
raise RuntimeError("Model not fitted. Call fit() first.")
|
|
110
|
+
|
|
111
|
+
# Compute kernel between X and training data
|
|
112
|
+
K_trans = self.kernel(X, self.X_train)
|
|
113
|
+
|
|
114
|
+
# Mean prediction
|
|
115
|
+
y_mean = np.dot(K_trans, self.alpha)
|
|
116
|
+
|
|
117
|
+
if return_std:
|
|
118
|
+
# Compute variance
|
|
119
|
+
if self.L is not None:
|
|
120
|
+
v = np.linalg.solve(self.L, K_trans.T)
|
|
121
|
+
K_pred = self.kernel(X)
|
|
122
|
+
y_var = K_pred - np.dot(v.T, v)
|
|
123
|
+
else:
|
|
124
|
+
K_inv = np.linalg.inv(self.kernel(self.X_train) + np.eye(len(self.X_train)) * self.noise)
|
|
125
|
+
K_pred = self.kernel(X)
|
|
126
|
+
y_var = K_pred - np.dot(K_trans, np.dot(K_inv, K_trans.T))
|
|
127
|
+
|
|
128
|
+
# Ensure variance is positive
|
|
129
|
+
diag = np.maximum(np.diag(y_var), 0)
|
|
130
|
+
return y_mean, np.sqrt(diag)
|
|
131
|
+
|
|
132
|
+
return y_mean
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class GPRStep:
|
|
136
|
+
"""GPR-based optimization step generator"""
|
|
137
|
+
|
|
138
|
+
def __init__(self):
|
|
139
|
+
# GPR model parameters
|
|
140
|
+
self.kernel_type = 'rbf'
|
|
141
|
+
self.length_scale_init = 1.0
|
|
142
|
+
self.amplitude_init = 1.0
|
|
143
|
+
self.noise_init = 1e-6
|
|
144
|
+
|
|
145
|
+
# Optimization strategy
|
|
146
|
+
self.balance_factor = 0.1
|
|
147
|
+
self.min_points_for_gpr = 5
|
|
148
|
+
self.max_history_points = 25
|
|
149
|
+
self.ucb_beta = 2.0
|
|
150
|
+
self.num_candidates = 25
|
|
151
|
+
self.candidate_scale = 1.0
|
|
152
|
+
self.max_step_norm = 0.5
|
|
153
|
+
|
|
154
|
+
# State variables
|
|
155
|
+
self.geom_history = []
|
|
156
|
+
self.energy_history = []
|
|
157
|
+
self.gradient_history = []
|
|
158
|
+
self.step_history = []
|
|
159
|
+
self.gpr = None
|
|
160
|
+
self.y_mean = 0.0
|
|
161
|
+
self.y_std = 1.0
|
|
162
|
+
self.iter = 0
|
|
163
|
+
self.failures = 0
|
|
164
|
+
|
|
165
|
+
def _update_histories(self, geometry, energy, gradient):
|
|
166
|
+
"""Update optimization histories"""
|
|
167
|
+
self.geom_history.append(geometry.copy())
|
|
168
|
+
self.energy_history.append(energy)
|
|
169
|
+
self.gradient_history.append(gradient.copy())
|
|
170
|
+
|
|
171
|
+
# Calculate and store step if possible
|
|
172
|
+
if len(self.geom_history) > 1:
|
|
173
|
+
step = geometry - self.geom_history[-2]
|
|
174
|
+
self.step_history.append(step)
|
|
175
|
+
|
|
176
|
+
# Limit history size
|
|
177
|
+
if len(self.geom_history) > self.max_history_points:
|
|
178
|
+
self.geom_history.pop(0)
|
|
179
|
+
self.energy_history.pop(0)
|
|
180
|
+
self.gradient_history.pop(0)
|
|
181
|
+
if len(self.step_history) > 0:
|
|
182
|
+
self.step_history.pop(0)
|
|
183
|
+
|
|
184
|
+
def _fit_gpr_model(self):
|
|
185
|
+
"""Fit GPR model to current data"""
|
|
186
|
+
if len(self.geom_history) < self.min_points_for_gpr:
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
# Prepare training data
|
|
191
|
+
X_train = np.array([g.flatten() for g in self.geom_history])
|
|
192
|
+
|
|
193
|
+
# Normalize energy values
|
|
194
|
+
y_train = np.array(self.energy_history)
|
|
195
|
+
self.y_mean = np.mean(y_train)
|
|
196
|
+
self.y_std = np.std(y_train) if np.std(y_train) > 1e-10 else 1.0
|
|
197
|
+
y_train_norm = (y_train - self.y_mean) / self.y_std
|
|
198
|
+
|
|
199
|
+
# Initialize or reuse GPR model
|
|
200
|
+
if self.gpr is None:
|
|
201
|
+
self.gpr = GaussianProcessRegression(
|
|
202
|
+
kernel='rbf',
|
|
203
|
+
length_scale=self.length_scale_init,
|
|
204
|
+
amplitude=self.amplitude_init,
|
|
205
|
+
noise=self.noise_init
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# Fit model
|
|
209
|
+
self.gpr.fit(X_train, y_train_norm)
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
except Exception as e:
|
|
213
|
+
print(f"Error fitting GPR model: {str(e)}")
|
|
214
|
+
self.failures += 1
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
def _generate_candidate_steps(self, current_geom, gradient):
|
|
218
|
+
"""Generate candidate steps for GPR evaluation"""
|
|
219
|
+
n_coords = len(current_geom)
|
|
220
|
+
candidates = []
|
|
221
|
+
|
|
222
|
+
# Negative gradient direction
|
|
223
|
+
if np.linalg.norm(gradient) > 1e-10:
|
|
224
|
+
unit_grad = gradient / np.linalg.norm(gradient)
|
|
225
|
+
neg_grad_step = -0.1 * self.candidate_scale * unit_grad
|
|
226
|
+
candidates.append(current_geom + neg_grad_step)
|
|
227
|
+
|
|
228
|
+
# Steps from previous history
|
|
229
|
+
if len(self.step_history) > 0:
|
|
230
|
+
for i in range(min(3, len(self.step_history))):
|
|
231
|
+
scale = np.random.uniform(0.8, 1.2) * self.candidate_scale
|
|
232
|
+
candidates.append(current_geom + scale * self.step_history[-(i+1)])
|
|
233
|
+
|
|
234
|
+
# Random steps
|
|
235
|
+
n_random = max(1, self.num_candidates - len(candidates))
|
|
236
|
+
step_scale = 0.1 * self.candidate_scale
|
|
237
|
+
if len(self.step_history) > 0:
|
|
238
|
+
step_scale = np.mean([np.linalg.norm(s) for s in self.step_history[-3:]]) * self.candidate_scale
|
|
239
|
+
|
|
240
|
+
for _ in range(n_random):
|
|
241
|
+
rand_dir = np.random.randn(*current_geom.shape)
|
|
242
|
+
rand_dir = rand_dir / max(1e-10, np.linalg.norm(rand_dir))
|
|
243
|
+
scale = np.random.uniform(0.5, 1.5) * step_scale
|
|
244
|
+
candidates.append(current_geom + scale * rand_dir)
|
|
245
|
+
|
|
246
|
+
return candidates
|
|
247
|
+
|
|
248
|
+
def _select_best_candidate(self, candidates, current_geom, current_energy):
|
|
249
|
+
"""Select best candidate based on GPR predictions"""
|
|
250
|
+
if self.gpr is None or not candidates:
|
|
251
|
+
return None, 0.0
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
# Prepare candidates for GPR
|
|
255
|
+
X_cand = np.array([g.flatten() for g in candidates])
|
|
256
|
+
|
|
257
|
+
# Get predictions with uncertainty
|
|
258
|
+
y_mean, y_std = self.gpr.predict(X_cand, return_std=True)
|
|
259
|
+
|
|
260
|
+
# Denormalize predictions
|
|
261
|
+
y_mean = y_mean * self.y_std + self.y_mean
|
|
262
|
+
y_std = y_std * self.y_std
|
|
263
|
+
|
|
264
|
+
# Acquisition function (Upper Confidence Bound)
|
|
265
|
+
acquisition = -(y_mean - self.ucb_beta * self.balance_factor * y_std)
|
|
266
|
+
|
|
267
|
+
# Select best candidate
|
|
268
|
+
best_idx = np.argmax(acquisition)
|
|
269
|
+
best_geom = candidates[best_idx]
|
|
270
|
+
best_step = best_geom - current_geom
|
|
271
|
+
|
|
272
|
+
# Expected improvement
|
|
273
|
+
expected_improvement = current_energy - y_mean[best_idx]
|
|
274
|
+
print(f"Expected energy: {y_mean[best_idx]:.6f}, uncertainty: {y_std[best_idx]:.6f}")
|
|
275
|
+
print(f"Expected improvement: {expected_improvement:.6f}")
|
|
276
|
+
|
|
277
|
+
return best_step, expected_improvement
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
print(f"Error in candidate selection: {str(e)}")
|
|
281
|
+
self.failures += 1
|
|
282
|
+
return None, 0.0
|
|
283
|
+
|
|
284
|
+
def run(self, geom_num_list, energy, gradient, original_move_vector):
|
|
285
|
+
"""Run GPR-based optimization step"""
|
|
286
|
+
print("GPR-Step optimization method")
|
|
287
|
+
n_coords = len(geom_num_list)
|
|
288
|
+
grad_rms = np.sqrt(np.mean(gradient ** 2))
|
|
289
|
+
|
|
290
|
+
print(f"Energy: {energy:.8f}, Gradient RMS: {grad_rms:.8f}")
|
|
291
|
+
|
|
292
|
+
# Update histories
|
|
293
|
+
self._update_histories(geom_num_list, energy, gradient)
|
|
294
|
+
|
|
295
|
+
# Use original step if not enough history
|
|
296
|
+
if len(self.geom_history) < self.min_points_for_gpr:
|
|
297
|
+
print(f"Building history ({len(self.geom_history)}/{self.min_points_for_gpr}), using original step")
|
|
298
|
+
self.iter += 1
|
|
299
|
+
return original_move_vector
|
|
300
|
+
|
|
301
|
+
# Use original step if too many GPR failures
|
|
302
|
+
if self.failures >= 3:
|
|
303
|
+
print(f"Too many GPR failures ({self.failures}), using original step")
|
|
304
|
+
self.iter += 1
|
|
305
|
+
return original_move_vector
|
|
306
|
+
|
|
307
|
+
# Fit GPR model
|
|
308
|
+
if not self._fit_gpr_model():
|
|
309
|
+
print("Failed to fit GPR model, using original step")
|
|
310
|
+
self.iter += 1
|
|
311
|
+
return original_move_vector
|
|
312
|
+
|
|
313
|
+
# Generate candidate steps
|
|
314
|
+
candidates = self._generate_candidate_steps(geom_num_list, gradient)
|
|
315
|
+
print(f"Generated {len(candidates)} candidate steps")
|
|
316
|
+
|
|
317
|
+
# Select best step
|
|
318
|
+
gpr_step, expected_improvement = self._select_best_candidate(
|
|
319
|
+
candidates, geom_num_list, energy)
|
|
320
|
+
|
|
321
|
+
if gpr_step is None or expected_improvement < 0:
|
|
322
|
+
print("GPR step selection failed or predicted energy increase, using original step")
|
|
323
|
+
self.iter += 1
|
|
324
|
+
return original_move_vector
|
|
325
|
+
|
|
326
|
+
# Blend steps
|
|
327
|
+
orig_norm = np.linalg.norm(original_move_vector)
|
|
328
|
+
gpr_norm = np.linalg.norm(gpr_step)
|
|
329
|
+
|
|
330
|
+
# Scale GPR step if it's too large
|
|
331
|
+
if gpr_norm > self.max_step_norm:
|
|
332
|
+
gpr_step = gpr_step * (self.max_step_norm / gpr_norm)
|
|
333
|
+
gpr_norm = self.max_step_norm
|
|
334
|
+
|
|
335
|
+
# Blend with original step
|
|
336
|
+
if orig_norm > 1e-10:
|
|
337
|
+
# Check if directions are similar
|
|
338
|
+
cos_angle = np.dot(original_move_vector.flatten(), gpr_step.flatten()) / (orig_norm * gpr_norm)
|
|
339
|
+
|
|
340
|
+
if cos_angle > 0.5: # Directions are similar
|
|
341
|
+
weight_gpr = 0.7
|
|
342
|
+
elif cos_angle > 0: # Somewhat aligned
|
|
343
|
+
weight_gpr = 0.5
|
|
344
|
+
else: # Different directions
|
|
345
|
+
weight_gpr = 0.3
|
|
346
|
+
|
|
347
|
+
# Ensure GPR step is not much larger than original
|
|
348
|
+
if gpr_norm > 3.0 * orig_norm:
|
|
349
|
+
gpr_step = gpr_step * (3.0 * orig_norm / gpr_norm)
|
|
350
|
+
|
|
351
|
+
blended_step = -1 * weight_gpr * gpr_step + (1.0 - weight_gpr) * original_move_vector
|
|
352
|
+
print(f"Using blended step: {weight_gpr:.2f}*GPR + {1.0-weight_gpr:.2f}*Original")
|
|
353
|
+
else:
|
|
354
|
+
blended_step = -1 * gpr_step
|
|
355
|
+
print("Using pure GPR step (original step near zero)")
|
|
356
|
+
|
|
357
|
+
# Final safety checks
|
|
358
|
+
if np.any(np.isnan(blended_step)) or np.any(np.isinf(blended_step)):
|
|
359
|
+
print("Numerical issues detected, using original step")
|
|
360
|
+
self.iter += 1
|
|
361
|
+
return original_move_vector
|
|
362
|
+
|
|
363
|
+
self.iter += 1
|
|
364
|
+
return blended_step
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import copy
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GradientDescent:
|
|
7
|
+
def __init__(self, **config):
|
|
8
|
+
#Pseudo-IRC
|
|
9
|
+
self.DELTA = 1.0
|
|
10
|
+
self.Initialization = True
|
|
11
|
+
self.config = config
|
|
12
|
+
self.hessian = None
|
|
13
|
+
self.bias_hessian = None
|
|
14
|
+
|
|
15
|
+
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=[]):
|
|
16
|
+
print("SD")
|
|
17
|
+
if self.Initialization:
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
self.Initialization = False
|
|
21
|
+
|
|
22
|
+
move_vector = self.DELTA * B_g
|
|
23
|
+
|
|
24
|
+
return move_vector#Bohr.
|
|
25
|
+
|
|
26
|
+
def set_hessian(self, hessian):
|
|
27
|
+
self.hessian = hessian
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
def set_bias_hessian(self, bias_hessian):
|
|
31
|
+
self.bias_hessian = bias_hessian
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
def get_hessian(self):
|
|
35
|
+
return self.hessian
|
|
36
|
+
|
|
37
|
+
def get_bias_hessian(self):
|
|
38
|
+
return self.bias_hessian
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class MassWeightedGradientDescent:
|
|
42
|
+
def __init__(self, **config):
|
|
43
|
+
#For (meta-)IRC (Euler method)
|
|
44
|
+
self.DELTA = 1.0
|
|
45
|
+
self.Initialization = True
|
|
46
|
+
self.config = config
|
|
47
|
+
self.element_list = None
|
|
48
|
+
self.atomic_mass = None
|
|
49
|
+
|
|
50
|
+
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):
|
|
51
|
+
print("MWSD")
|
|
52
|
+
if self.Initialization:
|
|
53
|
+
self.elem_mass_list = []
|
|
54
|
+
for elem in self.element_list:
|
|
55
|
+
self.elem_mass_list.append([self.atomic_mass(elem)])
|
|
56
|
+
self.elem_mass_list.append([self.atomic_mass(elem)])
|
|
57
|
+
self.elem_mass_list.append([self.atomic_mass(elem)])
|
|
58
|
+
self.elem_mass_list = np.array(self.elem_mass_list, dtype="float64")
|
|
59
|
+
self.Initialization = False
|
|
60
|
+
|
|
61
|
+
move_vector = self.DELTA * B_g / self.elem_mass_list
|
|
62
|
+
|
|
63
|
+
return move_vector#Bohr.
|
|
64
|
+
|
|
65
|
+
def set_hessian(self, hessian):
|
|
66
|
+
self.hessian = hessian
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
def set_bias_hessian(self, bias_hessian):
|
|
70
|
+
self.bias_hessian = bias_hessian
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def get_hessian(self):
|
|
75
|
+
return self.hessian
|
|
76
|
+
|
|
77
|
+
def get_bias_hessian(self):
|
|
78
|
+
return self.bias_hessian
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OptimizationAlgorithm(ABC):
|
|
6
|
+
"""Base class for optimization algorithms"""
|
|
7
|
+
|
|
8
|
+
@abstractmethod
|
|
9
|
+
def optimize(self, geometry_num_list, total_force_list, **kwargs):
|
|
10
|
+
"""Execute optimization step"""
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SteepestDescentOptimizer(OptimizationAlgorithm):
|
|
17
|
+
"""Steepest descent optimizer"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, config):
|
|
20
|
+
self.config = config
|
|
21
|
+
|
|
22
|
+
def optimize(self, geometry_num_list, total_force_list, **kwargs):
|
|
23
|
+
"""Steepest descent optimization"""
|
|
24
|
+
total_delta = []
|
|
25
|
+
delta = 0.5
|
|
26
|
+
for i in range(len(total_force_list)):
|
|
27
|
+
total_delta.append(delta * total_force_list[i])
|
|
28
|
+
|
|
29
|
+
# Apply edge constraints
|
|
30
|
+
if self.config.fix_init_edge:
|
|
31
|
+
move_vector = [total_delta[0] * 0.0]
|
|
32
|
+
else:
|
|
33
|
+
move_vector = [total_delta[0]]
|
|
34
|
+
|
|
35
|
+
for i in range(1, len(total_delta) - 1):
|
|
36
|
+
trust_radii_1 = np.linalg.norm(geometry_num_list[i] - geometry_num_list[i-1]) / 2.0
|
|
37
|
+
trust_radii_2 = np.linalg.norm(geometry_num_list[i] - geometry_num_list[i+1]) / 2.0
|
|
38
|
+
if np.linalg.norm(total_delta[i]) > trust_radii_1:
|
|
39
|
+
move_vector.append(total_delta[i] * trust_radii_1 / np.linalg.norm(total_delta[i]))
|
|
40
|
+
elif np.linalg.norm(total_delta[i]) > trust_radii_2:
|
|
41
|
+
move_vector.append(total_delta[i] * trust_radii_2 / np.linalg.norm(total_delta[i]))
|
|
42
|
+
else:
|
|
43
|
+
move_vector.append(total_delta[i])
|
|
44
|
+
|
|
45
|
+
if self.config.fix_end_edge:
|
|
46
|
+
move_vector.append(total_delta[-1] * 0.0)
|
|
47
|
+
else:
|
|
48
|
+
move_vector.append(total_delta[-1])
|
|
49
|
+
|
|
50
|
+
new_geometry = (geometry_num_list + move_vector) * self.config.bohr2angstroms
|
|
51
|
+
return new_geometry
|
|
52
|
+
|