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,516 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.optimize import minimize
|
|
3
|
+
|
|
4
|
+
from multioptpy.Parameters.parameter import (
|
|
5
|
+
covalent_radii_lib,
|
|
6
|
+
UFF_VDW_distance_lib,
|
|
7
|
+
number_element,
|
|
8
|
+
UnitValueLib,)
|
|
9
|
+
|
|
10
|
+
class IDPP:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
#ref.: arXiv:1406.1512v1
|
|
13
|
+
self.iteration = 2000
|
|
14
|
+
self.lr = 0.01
|
|
15
|
+
self.threshold = 1e-4
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
def calc_obj_func(self, idpp_dist_matrix, dist_matrix):
|
|
19
|
+
idpp_upper_triangle_indices = np.triu_indices(idpp_dist_matrix.shape[0], k=1)
|
|
20
|
+
idpp_upper_triangle_distances = idpp_dist_matrix[idpp_upper_triangle_indices]
|
|
21
|
+
dist_upper_triangle_indices = np.triu_indices(dist_matrix.shape[0], k=1)
|
|
22
|
+
dist_upper_triangle_distances = dist_matrix[dist_upper_triangle_indices]
|
|
23
|
+
weight_func = (dist_upper_triangle_distances + 1e-15) ** (-4)
|
|
24
|
+
obj_func = np.sum(weight_func * (idpp_upper_triangle_distances - dist_upper_triangle_distances) ** 2.0)
|
|
25
|
+
return obj_func
|
|
26
|
+
|
|
27
|
+
def calc_obj_func_1st_deriv(self, pos, idpp_dist_matrix, dist_matrix):
|
|
28
|
+
diff = pos[:, np.newaxis, :] - pos[np.newaxis, :, :] # Shape: (N, N, 3)
|
|
29
|
+
distances = np.linalg.norm(diff, axis=-1) # Shape: (N, N)
|
|
30
|
+
valid_mask = distances > 0
|
|
31
|
+
unit_diff = np.zeros_like(diff)
|
|
32
|
+
unit_diff[valid_mask] = diff[valid_mask] / (distances[valid_mask][:, np.newaxis] + 1e-15)
|
|
33
|
+
w = (distances + 1e-15)**(-4)
|
|
34
|
+
dw_dr = -4 * (distances + 1e-15)**(-5)
|
|
35
|
+
|
|
36
|
+
diff_matrix = idpp_dist_matrix - dist_matrix
|
|
37
|
+
d_obj_func_d_qij = (
|
|
38
|
+
(dw_dr * diff_matrix**2 - 2.0 * w * diff_matrix)[:, :, np.newaxis]
|
|
39
|
+
* unit_diff
|
|
40
|
+
) # Shape: (N, N, 3)
|
|
41
|
+
|
|
42
|
+
i_indices, j_indices = np.triu_indices(len(pos), k=1)
|
|
43
|
+
|
|
44
|
+
first_deriv = np.zeros_like(pos)
|
|
45
|
+
np.add.at(first_deriv, i_indices, d_obj_func_d_qij[i_indices, j_indices])
|
|
46
|
+
np.subtract.at(first_deriv, j_indices, d_obj_func_d_qij[i_indices, j_indices])
|
|
47
|
+
return first_deriv
|
|
48
|
+
|
|
49
|
+
def calc_idpp_dist_matrix(self, pos_list, n_node, number_of_node):
|
|
50
|
+
init_pos = pos_list[0]
|
|
51
|
+
term_pos = pos_list[-1]
|
|
52
|
+
init_pos_diff = init_pos[:, np.newaxis, :] - init_pos[np.newaxis, :, :]
|
|
53
|
+
init_pos_dist_matrix = np.sqrt(np.sum(init_pos_diff**2, axis=-1))
|
|
54
|
+
term_pos_diff = term_pos[:, np.newaxis, :] - term_pos[np.newaxis, :, :]
|
|
55
|
+
term_pos_dist_matrix = np.sqrt(np.sum(term_pos_diff**2, axis=-1))
|
|
56
|
+
idpp_dist_matrix = init_pos_dist_matrix + number_of_node * (term_pos_dist_matrix - init_pos_dist_matrix) / (n_node - 1)
|
|
57
|
+
|
|
58
|
+
return idpp_dist_matrix
|
|
59
|
+
|
|
60
|
+
def calc_dist_matrix(self, pos):
|
|
61
|
+
pos_diff = pos[:, np.newaxis, :] - pos[np.newaxis, :, :]
|
|
62
|
+
dist_matrix = np.sqrt(np.sum(pos_diff**2, axis=-1))
|
|
63
|
+
return dist_matrix
|
|
64
|
+
|
|
65
|
+
def get_func_and_deriv(self, pos_list, n_node, number_of_node):
|
|
66
|
+
dist_matrix = self.calc_dist_matrix(pos_list[number_of_node])
|
|
67
|
+
idpp_dist_matrix = self.calc_idpp_dist_matrix(pos_list, n_node, number_of_node)
|
|
68
|
+
obj_func = self.calc_obj_func(idpp_dist_matrix, dist_matrix)
|
|
69
|
+
first_deriv = self.calc_obj_func_1st_deriv(pos_list[number_of_node], idpp_dist_matrix, dist_matrix)
|
|
70
|
+
|
|
71
|
+
return obj_func, first_deriv
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def opt_path(self, geometry_list, element_list, memory_size=30):
|
|
75
|
+
"""
|
|
76
|
+
Optimize the path using L-BFGS algorithm with the original step size limiting.
|
|
77
|
+
|
|
78
|
+
Parameters:
|
|
79
|
+
-----------
|
|
80
|
+
geometry_list : list of numpy arrays
|
|
81
|
+
List of geometries to optimize
|
|
82
|
+
element_list : list
|
|
83
|
+
List of elements (preserved for compatibility)
|
|
84
|
+
memory_size : int
|
|
85
|
+
Number of correction pairs to store for L-BFGS
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
--------
|
|
89
|
+
list of numpy arrays
|
|
90
|
+
Optimized geometries
|
|
91
|
+
"""
|
|
92
|
+
print("IDPP Optimization with L-BFGS")
|
|
93
|
+
|
|
94
|
+
# Initialize L-BFGS memory for each image
|
|
95
|
+
s_list = [[] for _ in range(len(geometry_list))]
|
|
96
|
+
y_list = [[] for _ in range(len(geometry_list))]
|
|
97
|
+
rho_list = [[] for _ in range(len(geometry_list))]
|
|
98
|
+
|
|
99
|
+
def lbfgs_direction(gradient, j):
|
|
100
|
+
"""Compute the L-BFGS search direction using two-loop recursion"""
|
|
101
|
+
if len(s_list[j]) == 0:
|
|
102
|
+
return -gradient
|
|
103
|
+
|
|
104
|
+
q = gradient.copy()
|
|
105
|
+
alpha_list = []
|
|
106
|
+
|
|
107
|
+
# First loop: compute alpha values and update q
|
|
108
|
+
for i in range(len(s_list[j])-1, -1, -1):
|
|
109
|
+
alpha = rho_list[j][i] * np.sum(s_list[j][i] * q)
|
|
110
|
+
alpha_list.insert(0, alpha)
|
|
111
|
+
q = q - alpha * y_list[j][i]
|
|
112
|
+
|
|
113
|
+
# Scale with gamma
|
|
114
|
+
i = len(s_list[j]) - 1
|
|
115
|
+
denominator = np.sum(y_list[j][i] * y_list[j][i]) # Avoid division by zero
|
|
116
|
+
if np.abs(denominator) > 1e-10:
|
|
117
|
+
gamma = np.sum(s_list[j][i] * y_list[j][i]) / denominator
|
|
118
|
+
else:
|
|
119
|
+
gamma = 0
|
|
120
|
+
r = gamma * q
|
|
121
|
+
|
|
122
|
+
# Second loop: compute search direction
|
|
123
|
+
for i in range(len(s_list[j])):
|
|
124
|
+
beta = rho_list[j][i] * np.sum(y_list[j][i] * r)
|
|
125
|
+
r = r + s_list[j][i] * (alpha_list[i] - beta)
|
|
126
|
+
|
|
127
|
+
return -r
|
|
128
|
+
|
|
129
|
+
for i in range(self.iteration):
|
|
130
|
+
obj_func_list = []
|
|
131
|
+
|
|
132
|
+
for j in range(len(geometry_list)):
|
|
133
|
+
if j == 0 or j == len(geometry_list) - 1:
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# Save current position for computing displacement later
|
|
137
|
+
current_pos = geometry_list[j].copy()
|
|
138
|
+
|
|
139
|
+
# Get objective function and gradient
|
|
140
|
+
obj_func, gradient = self.get_func_and_deriv(geometry_list, len(geometry_list), j)
|
|
141
|
+
obj_func_list.append(obj_func)
|
|
142
|
+
gradient *= -1
|
|
143
|
+
# Compute search direction using L-BFGS
|
|
144
|
+
search_dir = lbfgs_direction(gradient, j)
|
|
145
|
+
|
|
146
|
+
# Apply the original step size limiting algorithm
|
|
147
|
+
step_norm = min(self.lr, np.linalg.norm(search_dir))
|
|
148
|
+
if np.linalg.norm(search_dir) > 1e-10: # Avoid division by zero
|
|
149
|
+
norm_step = search_dir / np.linalg.norm(search_dir)
|
|
150
|
+
geometry_list[j] -= step_norm * norm_step
|
|
151
|
+
|
|
152
|
+
# Update L-BFGS memory after taking the step
|
|
153
|
+
new_obj_func, new_gradient = self.get_func_and_deriv(geometry_list, len(geometry_list), j)
|
|
154
|
+
|
|
155
|
+
# Compute s and y vectors
|
|
156
|
+
s = geometry_list[j] - current_pos
|
|
157
|
+
y = new_gradient - gradient
|
|
158
|
+
|
|
159
|
+
# Only update memory if curvature condition is satisfied
|
|
160
|
+
sy_product = np.sum(s * y)
|
|
161
|
+
if sy_product > 1e-10:
|
|
162
|
+
# Manage memory size
|
|
163
|
+
if len(s_list[j]) >= memory_size:
|
|
164
|
+
s_list[j].pop(0)
|
|
165
|
+
y_list[j].pop(0)
|
|
166
|
+
rho_list[j].pop(0)
|
|
167
|
+
|
|
168
|
+
s_list[j].append(s)
|
|
169
|
+
y_list[j].append(y)
|
|
170
|
+
rho_list[j].append(1.0 / sy_product)
|
|
171
|
+
|
|
172
|
+
if i % 200 == 0:
|
|
173
|
+
print("ITR: ", i)
|
|
174
|
+
print("Objective function (Max): ", max(obj_func_list))
|
|
175
|
+
|
|
176
|
+
if max(obj_func_list) < self.threshold:
|
|
177
|
+
print("ITR: ", i)
|
|
178
|
+
print("IDPP Converged!!!")
|
|
179
|
+
break
|
|
180
|
+
|
|
181
|
+
print("IDPP Optimization Done.")
|
|
182
|
+
return geometry_list
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class CFB_ENM:
|
|
187
|
+
"""
|
|
188
|
+
Implements a standalone Correlated Flat-Bottom Elastic Network Model (CFB-ENM)
|
|
189
|
+
for optimizing reaction paths. The potential is based on the logic from dmf.py.
|
|
190
|
+
|
|
191
|
+
This class identifies quartets of atoms involved in bond-making and -breaking
|
|
192
|
+
events between a reactant and product structure. It then applies a specialized
|
|
193
|
+
potential function to these quartets to guide the path optimization.
|
|
194
|
+
|
|
195
|
+
The path is optimized using an L-BFGS algorithm implemented from scratch.
|
|
196
|
+
ref. : S.-i. Koda and S. Saito, Flat-bottom Elastic Network Model for Generating Improved Plausible Reaction Paths, JCTC, 20, 7176−7187 (2024). doi: 10.1021/acs.jctc.4c00792
|
|
197
|
+
S.-i. Koda and S. Saito, Correlated Flat-bottom Elastic Network Model for Improved Bond Rearrangement in Reaction Paths, JCTC, 21, 3513−3522 (2025). doi: 10.1021/acs.jctc.4c01549
|
|
198
|
+
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
def __init__(self, iteration=2000, lr=0.01, threshold=1e-4, bond_scale=1.25,
|
|
202
|
+
corr0_scale=1.10, corr1_scale=1.50, corr2_scale=1.60,
|
|
203
|
+
eps=0.05, pivotal=True, single=True, remove_fourmembered=True):
|
|
204
|
+
"""
|
|
205
|
+
Initializes the CFB_ENM optimizer.
|
|
206
|
+
|
|
207
|
+
Parameters:
|
|
208
|
+
-----------
|
|
209
|
+
iteration : int
|
|
210
|
+
Maximum number of optimization iterations.
|
|
211
|
+
lr : float
|
|
212
|
+
Learning rate or maximum step size for the L-BFGS update.
|
|
213
|
+
threshold : float
|
|
214
|
+
Convergence threshold for the objective function.
|
|
215
|
+
bond_scale : float
|
|
216
|
+
Factor to determine bonding from covalent radii.
|
|
217
|
+
corr0_scale, corr1_scale, corr2_scale : float
|
|
218
|
+
Scaling factors for correlation distance thresholds.
|
|
219
|
+
eps : float
|
|
220
|
+
Smoothing parameter for the potential function.
|
|
221
|
+
pivotal, single, remove_fourmembered : bool
|
|
222
|
+
Flags to control quartet identification logic.
|
|
223
|
+
"""
|
|
224
|
+
self.iteration = int(iteration) # FIX: Ensure iteration is an integer
|
|
225
|
+
self.lr = lr
|
|
226
|
+
self.threshold = threshold
|
|
227
|
+
|
|
228
|
+
# Parameters for CFB_ENM potential from dmf.py
|
|
229
|
+
self.bond_scale = bond_scale
|
|
230
|
+
self.corr0_scale = corr0_scale
|
|
231
|
+
self.corr1_scale = corr1_scale
|
|
232
|
+
self.corr2_scale = corr2_scale
|
|
233
|
+
self.eps = eps
|
|
234
|
+
self.pivotal = pivotal
|
|
235
|
+
self.single = single
|
|
236
|
+
self.remove_fourmembered = remove_fourmembered
|
|
237
|
+
|
|
238
|
+
# These will be populated by _initialize_potential
|
|
239
|
+
self.quartets = []
|
|
240
|
+
self.d_corr0 = None
|
|
241
|
+
self.d_corr1 = None
|
|
242
|
+
self.d_corr2 = None
|
|
243
|
+
self.bohr2ang = UnitValueLib().bohr2angstroms
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
def _get_connectivity_matrix(self, pos, element_list):
|
|
247
|
+
"""
|
|
248
|
+
Determines the adjacency matrix for a given geometry.
|
|
249
|
+
"""
|
|
250
|
+
radii = np.array([covalent_radii_lib(el) * self.bohr2ang for el in element_list])
|
|
251
|
+
r_cov = radii[:, np.newaxis] + radii[np.newaxis, :]
|
|
252
|
+
|
|
253
|
+
dist_matrix = self.calc_dist_matrix(pos)
|
|
254
|
+
|
|
255
|
+
J = (dist_matrix / r_cov) < self.bond_scale
|
|
256
|
+
np.fill_diagonal(J, False)
|
|
257
|
+
return J, dist_matrix
|
|
258
|
+
|
|
259
|
+
def _get_quartets(self, J_only_r, J_only_p, J_both,
|
|
260
|
+
pivotal=True, single=True, remove_fourmembered=True):
|
|
261
|
+
"""
|
|
262
|
+
Identifies quartets of atoms involved in correlated motion.
|
|
263
|
+
This is a direct adaptation of the logic from dmf.py.
|
|
264
|
+
"""
|
|
265
|
+
J2 = np.dot(J_both, J_both)
|
|
266
|
+
quartets = []
|
|
267
|
+
|
|
268
|
+
if pivotal:
|
|
269
|
+
if single:
|
|
270
|
+
pivots = np.where((np.sum(J_only_r, axis=1) == 1)
|
|
271
|
+
& (np.sum(J_only_p, axis=1) == 1))[0]
|
|
272
|
+
else:
|
|
273
|
+
pivots = np.where(np.any(J_only_r, axis=1)
|
|
274
|
+
& np.any(J_only_p, axis=1))[0]
|
|
275
|
+
for i in pivots:
|
|
276
|
+
only_r = np.where(J_only_r[i])[0]
|
|
277
|
+
only_p = np.where(J_only_p[i])[0]
|
|
278
|
+
for j in only_r:
|
|
279
|
+
for k in only_p:
|
|
280
|
+
if not (remove_fourmembered and J2[j, k]):
|
|
281
|
+
quartets.append([i, j, i, k])
|
|
282
|
+
else:
|
|
283
|
+
# Non-pivotal logic (adapted from dmf.py)
|
|
284
|
+
pairs_only_r = list(zip(*np.where(np.triu(J_only_r, k=1))))
|
|
285
|
+
pairs_only_p = list(zip(*np.where(np.triu(J_only_p, k=1))))
|
|
286
|
+
|
|
287
|
+
for pr in pairs_only_r:
|
|
288
|
+
for pp in pairs_only_p:
|
|
289
|
+
q = list(pr) + list(pp)
|
|
290
|
+
is_fourmembered = False
|
|
291
|
+
if remove_fourmembered:
|
|
292
|
+
unique_atoms = set(q)
|
|
293
|
+
if len(unique_atoms) == 4:
|
|
294
|
+
is_fourmembered = (J_both[q[0], q[2]] and J_both[q[1], q[3]]) or \
|
|
295
|
+
(J_both[q[0], q[3]] and J_both[q[1], q[2]])
|
|
296
|
+
elif len(unique_atoms) == 3:
|
|
297
|
+
# Find the two atoms that appear once
|
|
298
|
+
counts = {atom: q.count(atom) for atom in unique_atoms}
|
|
299
|
+
uniq_idxs = [atom for atom, count in counts.items() if count == 1]
|
|
300
|
+
if len(uniq_idxs) == 2:
|
|
301
|
+
is_fourmembered = J2[uniq_idxs[0], uniq_idxs[1]]
|
|
302
|
+
|
|
303
|
+
if not is_fourmembered:
|
|
304
|
+
quartets.append(q)
|
|
305
|
+
return quartets
|
|
306
|
+
|
|
307
|
+
def _initialize_potential(self, reactant_pos, product_pos, element_list):
|
|
308
|
+
"""
|
|
309
|
+
Initializes the parameters for the CFB-ENM potential function based on
|
|
310
|
+
reactant and product structures.
|
|
311
|
+
"""
|
|
312
|
+
natoms = len(element_list)
|
|
313
|
+
images = [reactant_pos, product_pos]
|
|
314
|
+
|
|
315
|
+
Js = []
|
|
316
|
+
d_bonds_list = []
|
|
317
|
+
for pos in images:
|
|
318
|
+
J, d = self._get_connectivity_matrix(pos, element_list)
|
|
319
|
+
Js.append(J)
|
|
320
|
+
d_bonds_list.append(np.where(J, d, 0.0))
|
|
321
|
+
|
|
322
|
+
d_bond = np.max(np.array(d_bonds_list), axis=0)
|
|
323
|
+
|
|
324
|
+
J_only_r = Js[0] & (~Js[1])
|
|
325
|
+
J_only_p = Js[1] & (~Js[0])
|
|
326
|
+
J_both = Js[0] & Js[1]
|
|
327
|
+
|
|
328
|
+
self.quartets = self._get_quartets(J_only_r, J_only_p, J_both,
|
|
329
|
+
self.pivotal, self.single, self.remove_fourmembered)
|
|
330
|
+
|
|
331
|
+
self.d_corr0 = self.corr0_scale * d_bond
|
|
332
|
+
self.d_corr1 = self.corr1_scale * d_bond
|
|
333
|
+
self.d_corr2 = self.corr2_scale * d_bond
|
|
334
|
+
|
|
335
|
+
# Ensure diagonal is zero
|
|
336
|
+
I = np.identity(natoms, dtype='bool')
|
|
337
|
+
self.d_corr0[I] = 0.0
|
|
338
|
+
self.d_corr1[I] = 0.0
|
|
339
|
+
self.d_corr2[I] = 0.0
|
|
340
|
+
|
|
341
|
+
print(f"CFB-ENM: Initialized potential with {len(self.quartets)} quartets.")
|
|
342
|
+
|
|
343
|
+
def calc_dist_matrix(self, pos):
|
|
344
|
+
"""
|
|
345
|
+
Calculates the pairwise distance matrix for a given geometry.
|
|
346
|
+
"""
|
|
347
|
+
pos_diff = pos[:, np.newaxis, :] - pos[np.newaxis, :, :]
|
|
348
|
+
dist_matrix = np.sqrt(np.sum(pos_diff**2, axis=-1))
|
|
349
|
+
return dist_matrix
|
|
350
|
+
|
|
351
|
+
def get_func_and_deriv(self, pos):
|
|
352
|
+
"""
|
|
353
|
+
Calculates the CFB-ENM objective function and its analytical gradient
|
|
354
|
+
for a single image, based on the quartet potential from dmf.py.
|
|
355
|
+
"""
|
|
356
|
+
natoms = pos.shape[0]
|
|
357
|
+
r = pos
|
|
358
|
+
dr = r[:, np.newaxis, :] - r
|
|
359
|
+
d = np.sqrt(np.sum(dr**2, axis=-1))
|
|
360
|
+
|
|
361
|
+
energy = 0.0
|
|
362
|
+
forces = np.zeros_like(pos)
|
|
363
|
+
|
|
364
|
+
d_d0 = d - self.d_corr0
|
|
365
|
+
d1_d0 = self.d_corr1 - self.d_corr0
|
|
366
|
+
d2_d0 = self.d_corr2 - self.d_corr0
|
|
367
|
+
|
|
368
|
+
for t in self.quartets:
|
|
369
|
+
# t = [atom1, atom2, atom3, atom4]
|
|
370
|
+
# Bond pair 1: (t[0], t[1]), Bond pair 2: (t[2], t[3])
|
|
371
|
+
|
|
372
|
+
# Check if atoms are in the repulsive region
|
|
373
|
+
if (d_d0[t[0], t[1]] > 0.0 and d_d0[t[2], t[3]] > 0.0):
|
|
374
|
+
|
|
375
|
+
pp = (d_d0[t[0], t[1]] * d_d0[t[2], t[3]]
|
|
376
|
+
- d1_d0[t[0], t[1]] * d1_d0[t[2], t[3]])
|
|
377
|
+
|
|
378
|
+
# Check if potential is active
|
|
379
|
+
if pp > 0.0:
|
|
380
|
+
dnm = (d2_d0[t[0], t[1]] * d2_d0[t[2], t[3]]
|
|
381
|
+
- d1_d0[t[0], t[1]] * d1_d0[t[2], t[3]])
|
|
382
|
+
|
|
383
|
+
# Avoid division by zero
|
|
384
|
+
if abs(dnm) < 1e-10: continue
|
|
385
|
+
|
|
386
|
+
pp_norm = pp / dnm
|
|
387
|
+
sqrt_pp2_eps2 = np.sqrt(pp_norm**2 + self.eps**2)
|
|
388
|
+
|
|
389
|
+
energy += sqrt_pp2_eps2 - self.eps
|
|
390
|
+
|
|
391
|
+
# Common factor for gradient
|
|
392
|
+
alpha = pp_norm / sqrt_pp2_eps2
|
|
393
|
+
|
|
394
|
+
# Gradient vectors
|
|
395
|
+
v1 = d_d0[t[2], t[3]] / d[t[0], t[1]] * (r[t[0]] - r[t[1]])
|
|
396
|
+
v2 = d_d0[t[0], t[1]] / d[t[2], t[3]] * (r[t[2]] - r[t[3]])
|
|
397
|
+
|
|
398
|
+
v1_norm = v1 / dnm
|
|
399
|
+
v2_norm = v2 / dnm
|
|
400
|
+
|
|
401
|
+
# Accumulate forces
|
|
402
|
+
forces[t[0]] -= alpha * v1_norm
|
|
403
|
+
forces[t[1]] += alpha * v1_norm
|
|
404
|
+
forces[t[2]] -= alpha * v2_norm
|
|
405
|
+
forces[t[3]] += alpha * v2_norm
|
|
406
|
+
|
|
407
|
+
# The optimizer expects the gradient of the objective function.
|
|
408
|
+
# Force is the negative of the gradient.
|
|
409
|
+
gradient = -forces
|
|
410
|
+
|
|
411
|
+
return energy, gradient
|
|
412
|
+
|
|
413
|
+
def opt_path(self, geometry_list, element_list, memory_size=30):
|
|
414
|
+
"""
|
|
415
|
+
Optimize the path using L-BFGS algorithm.
|
|
416
|
+
|
|
417
|
+
Parameters:
|
|
418
|
+
-----------
|
|
419
|
+
geometry_list : list of np.ndarray
|
|
420
|
+
List of geometries (images) to optimize. The first and last are
|
|
421
|
+
fixed as reactant and product.
|
|
422
|
+
element_list : list of str
|
|
423
|
+
List of element symbols for the atoms.
|
|
424
|
+
memory_size : int
|
|
425
|
+
Number of correction pairs to store for L-BFGS.
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
--------
|
|
429
|
+
list of np.ndarray
|
|
430
|
+
The list of optimized geometries.
|
|
431
|
+
"""
|
|
432
|
+
print("CFB-ENM Optimization with L-BFGS")
|
|
433
|
+
|
|
434
|
+
# Initialize the potential based on the start and end points of the path
|
|
435
|
+
self._initialize_potential(geometry_list[0], geometry_list[-1], element_list)
|
|
436
|
+
|
|
437
|
+
# Initialize L-BFGS memory for each image
|
|
438
|
+
s_list = [[] for _ in range(len(geometry_list))]
|
|
439
|
+
y_list = [[] for _ in range(len(geometry_list))]
|
|
440
|
+
rho_list = [[] for _ in range(len(geometry_list))]
|
|
441
|
+
|
|
442
|
+
def lbfgs_direction(gradient, j):
|
|
443
|
+
"""Compute the L-BFGS search direction using two-loop recursion"""
|
|
444
|
+
if len(s_list[j]) == 0:
|
|
445
|
+
return -gradient
|
|
446
|
+
|
|
447
|
+
q = gradient.copy()
|
|
448
|
+
alpha_list = []
|
|
449
|
+
|
|
450
|
+
for i in range(len(s_list[j])-1, -1, -1):
|
|
451
|
+
alpha = rho_list[j][i] * np.sum(s_list[j][i] * q)
|
|
452
|
+
alpha_list.insert(0, alpha)
|
|
453
|
+
q -= alpha * y_list[j][i]
|
|
454
|
+
|
|
455
|
+
i = len(s_list[j]) - 1
|
|
456
|
+
denominator = np.sum(y_list[j][i] * y_list[j][i])
|
|
457
|
+
if np.abs(denominator) > 1e-10:
|
|
458
|
+
gamma = np.sum(s_list[j][i] * y_list[j][i]) / denominator
|
|
459
|
+
else:
|
|
460
|
+
gamma = 1.0 # Fallback to steepest descent scaling
|
|
461
|
+
r = gamma * q
|
|
462
|
+
|
|
463
|
+
for i in range(len(s_list[j])):
|
|
464
|
+
beta = rho_list[j][i] * np.sum(y_list[j][i] * r)
|
|
465
|
+
r += s_list[j][i] * (alpha_list[i] - beta)
|
|
466
|
+
|
|
467
|
+
return -r
|
|
468
|
+
|
|
469
|
+
# FIX: Ensure self.iteration is an integer before using in range()
|
|
470
|
+
for i in range(int(self.iteration)):
|
|
471
|
+
obj_func_list = []
|
|
472
|
+
|
|
473
|
+
# Iterate over intermediate images (endpoints are fixed)
|
|
474
|
+
for j in range(1, len(geometry_list) - 1):
|
|
475
|
+
current_pos = geometry_list[j].copy()
|
|
476
|
+
|
|
477
|
+
obj_func, gradient = self.get_func_and_deriv(current_pos)
|
|
478
|
+
obj_func_list.append(obj_func)
|
|
479
|
+
|
|
480
|
+
search_dir = lbfgs_direction(gradient, j)
|
|
481
|
+
|
|
482
|
+
# Simple step size control
|
|
483
|
+
step_norm = self.lr
|
|
484
|
+
if np.linalg.norm(search_dir) > 1e-10:
|
|
485
|
+
norm_step = search_dir / np.linalg.norm(search_dir)
|
|
486
|
+
geometry_list[j] += step_norm * norm_step
|
|
487
|
+
|
|
488
|
+
# Update L-BFGS memory
|
|
489
|
+
new_pos = geometry_list[j]
|
|
490
|
+
_, new_gradient = self.get_func_and_deriv(new_pos)
|
|
491
|
+
|
|
492
|
+
s = new_pos - current_pos
|
|
493
|
+
y = new_gradient - gradient
|
|
494
|
+
|
|
495
|
+
sy_product = np.sum(s * y)
|
|
496
|
+
if sy_product > 1e-10: # Curvature condition
|
|
497
|
+
if len(s_list[j]) >= memory_size:
|
|
498
|
+
s_list[j].pop(0)
|
|
499
|
+
y_list[j].pop(0)
|
|
500
|
+
rho_list[j].pop(0)
|
|
501
|
+
|
|
502
|
+
s_list[j].append(s)
|
|
503
|
+
y_list[j].append(y)
|
|
504
|
+
rho_list[j].append(1.0 / sy_product)
|
|
505
|
+
|
|
506
|
+
if i % 200 == 0:
|
|
507
|
+
max_obj = max(obj_func_list) if obj_func_list else 0.0
|
|
508
|
+
print(f"ITR: {i}, Objective function (Max): {max_obj:.6e}")
|
|
509
|
+
|
|
510
|
+
if not obj_func_list or max(obj_func_list) < self.threshold:
|
|
511
|
+
print(f"ITR: {i}")
|
|
512
|
+
print("CFB-ENM Converged!!!")
|
|
513
|
+
break
|
|
514
|
+
|
|
515
|
+
print("CFB-ENM Optimization Done.")
|
|
516
|
+
return geometry_list
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from multioptpy.Parameters.parameter import UnitValueLib
|
|
2
|
+
from multioptpy.Utils.calc_tools import torch_calc_angle_from_vec
|
|
3
|
+
|
|
4
|
+
import torch
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class StructKeepAnglePotential:
|
|
8
|
+
def __init__(self, **kwarg):
|
|
9
|
+
self.config = kwarg
|
|
10
|
+
UVL = UnitValueLib()
|
|
11
|
+
self.hartree2kcalmol = UVL.hartree2kcalmol
|
|
12
|
+
self.bohr2angstroms = UVL.bohr2angstroms
|
|
13
|
+
self.hartree2kjmol = UVL.hartree2kjmol
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
def calc_energy(self, geom_num_list, bias_pot_params=[]):
|
|
17
|
+
"""
|
|
18
|
+
# required variables: self.config["keep_angle_atom_pairs"],
|
|
19
|
+
self.config["keep_angle_spring_const"]
|
|
20
|
+
self.config["keep_angle_angle"]
|
|
21
|
+
bias_pot_params[0] : keep_angle_spring_const
|
|
22
|
+
bias_pot_params[1] : keep_angle_angle
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
vector1 = geom_num_list[self.config["keep_angle_atom_pairs"][0]-1] - geom_num_list[self.config["keep_angle_atom_pairs"][1]-1]
|
|
26
|
+
vector2 = geom_num_list[self.config["keep_angle_atom_pairs"][2]-1] - geom_num_list[self.config["keep_angle_atom_pairs"][1]-1]
|
|
27
|
+
theta = torch_calc_angle_from_vec(vector1, vector2)
|
|
28
|
+
if len(bias_pot_params) == 0:
|
|
29
|
+
energy = 0.5 * self.config["keep_angle_spring_const"] * (theta - torch.deg2rad(torch.tensor(self.config["keep_angle_angle"]))) ** 2
|
|
30
|
+
else:
|
|
31
|
+
energy = 0.5 * bias_pot_params[0] * (theta - torch.deg2rad(bias_pot_params[1])) ** 2
|
|
32
|
+
return energy #hartree
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class StructKeepAnglePotentialv2:
|
|
37
|
+
def __init__(self, **kwarg):
|
|
38
|
+
self.config = kwarg
|
|
39
|
+
UVL = UnitValueLib()
|
|
40
|
+
self.hartree2kcalmol = UVL.hartree2kcalmol
|
|
41
|
+
self.bohr2angstroms = UVL.bohr2angstroms
|
|
42
|
+
self.hartree2kjmol = UVL.hartree2kjmol
|
|
43
|
+
return
|
|
44
|
+
def calc_energy(self, geom_num_list, bias_pot_params=[]):
|
|
45
|
+
"""
|
|
46
|
+
# required variables: self.config["keep_angle_v2_spring_const"],
|
|
47
|
+
self.config["keep_angle_v2_angle"],
|
|
48
|
+
self.config["keep_angle_v2_fragm1"],
|
|
49
|
+
self.config["keep_angle_v2_fragm2"],
|
|
50
|
+
self.config["keep_angle_v2_fragm3"],
|
|
51
|
+
bias_pot_params[0] : keep_angle_v2_spring_const
|
|
52
|
+
bias_pot_params[1] : keep_angle_v2_angle
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
fragm_1_center = torch.mean(geom_num_list[torch.tensor(self.config["keep_angle_v2_fragm1"]) - 1], dim=0)
|
|
56
|
+
fragm_2_center = torch.mean(geom_num_list[torch.tensor(self.config["keep_angle_v2_fragm2"]) - 1], dim=0)
|
|
57
|
+
fragm_3_center = torch.mean(geom_num_list[torch.tensor(self.config["keep_angle_v2_fragm3"]) - 1], dim=0)
|
|
58
|
+
|
|
59
|
+
vector1 = fragm_1_center - fragm_2_center
|
|
60
|
+
vector2 = fragm_3_center - fragm_2_center
|
|
61
|
+
theta = torch_calc_angle_from_vec(vector1, vector2)
|
|
62
|
+
if len(bias_pot_params) == 0:
|
|
63
|
+
energy = 0.5 * self.config["keep_angle_v2_spring_const"] * (theta - torch.deg2rad(torch.tensor(self.config["keep_angle_v2_angle"]))) ** 2
|
|
64
|
+
else:
|
|
65
|
+
energy = 0.5 * bias_pot_params[0] * (theta - torch.deg2rad(bias_pot_params[1])) ** 2
|
|
66
|
+
return energy #hartree
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class StructKeepAnglePotentialAtomDistDependent:
|
|
70
|
+
def __init__(self, **kwarg):
|
|
71
|
+
self.config = kwarg
|
|
72
|
+
UVL = UnitValueLib()
|
|
73
|
+
self.hartree2kcalmol = UVL.hartree2kcalmol
|
|
74
|
+
self.bohr2angstroms = UVL.bohr2angstroms
|
|
75
|
+
self.hartree2kjmol = UVL.hartree2kjmol
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
def calc_energy(self, geom_num_list, bias_pot_params=[]):
|
|
79
|
+
"""
|
|
80
|
+
# required variables: self.config["aDD_keep_angle_spring_const"]
|
|
81
|
+
self.config["aDD_keep_angle_min_angle"]
|
|
82
|
+
self.config["aDD_keep_angle_max_angle"]
|
|
83
|
+
self.config["aDD_keep_angle_base_dist"]
|
|
84
|
+
self.config["aDD_keep_angle_reference_atom"]
|
|
85
|
+
self.config["aDD_keep_angle_center_atom"]
|
|
86
|
+
self.config["aDD_keep_angle_atoms"]
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
energy = 0.0
|
|
90
|
+
self.config["keep_angle_spring_const"] = self.config["aDD_keep_angle_spring_const"]
|
|
91
|
+
max_angle = torch.tensor(self.config["aDD_keep_angle_max_angle"])
|
|
92
|
+
min_angle = torch.tensor(self.config["aDD_keep_angle_min_angle"])
|
|
93
|
+
ref_dist = torch.linalg.norm(geom_num_list[self.config["aDD_keep_angle_center_atom"]-1] - geom_num_list[self.config["aDD_keep_angle_reference_atom"]-1]) / self.bohr2angstroms
|
|
94
|
+
base_dist = self.config["aDD_keep_angle_base_dist"] / self.bohr2angstroms
|
|
95
|
+
eq_angle = min_angle + ((max_angle - min_angle)/(1 + torch.exp(-(ref_dist - base_dist))))
|
|
96
|
+
|
|
97
|
+
bias_pot_params = [self.config["aDD_keep_angle_spring_const"], eq_angle]
|
|
98
|
+
KAP = StructKeepAnglePotential(keep_angle_angle=eq_angle, keep_angle_spring_const=self.config["aDD_keep_angle_spring_const"], keep_angle_atom_pairs=[self.config["aDD_keep_angle_atoms"][0], self.config["aDD_keep_angle_center_atom"], self.config["aDD_keep_angle_atoms"][1]])
|
|
99
|
+
|
|
100
|
+
energy += KAP.calc_energy(geom_num_list, bias_pot_params)
|
|
101
|
+
|
|
102
|
+
KAP = StructKeepAnglePotential(keep_angle_angle=eq_angle, keep_angle_spring_const=self.config["aDD_keep_angle_spring_const"], keep_angle_atom_pairs=[self.config["aDD_keep_angle_atoms"][2], self.config["aDD_keep_angle_center_atom"], self.config["aDD_keep_angle_atoms"][1]])
|
|
103
|
+
energy += KAP.calc_energy(geom_num_list, bias_pot_params)
|
|
104
|
+
|
|
105
|
+
KAP = StructKeepAnglePotential(keep_angle_angle=eq_angle, keep_angle_spring_const=self.config["aDD_keep_angle_spring_const"], keep_angle_atom_pairs=[self.config["aDD_keep_angle_atoms"][0], self.config["aDD_keep_angle_center_atom"], self.config["aDD_keep_angle_atoms"][2]])
|
|
106
|
+
energy += KAP.calc_energy(geom_num_list, bias_pot_params)
|
|
107
|
+
|
|
108
|
+
return energy
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class StructKeepAnglePotentialLonePairAngle:
|
|
112
|
+
def __init__(self, **kwarg):
|
|
113
|
+
self.config = kwarg
|
|
114
|
+
UVL = UnitValueLib()
|
|
115
|
+
self.hartree2kcalmol = UVL.hartree2kcalmol
|
|
116
|
+
self.bohr2angstroms = UVL.bohr2angstroms
|
|
117
|
+
self.hartree2kjmol = UVL.hartree2kjmol
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
def calc_energy(self, geom_num_list, bias_pot_params=[]):
|
|
121
|
+
"""
|
|
122
|
+
# required variables: self.config["lone_pair_keep_angle_spring_const"]
|
|
123
|
+
self.config["lone_pair_keep_angle_angle"]
|
|
124
|
+
self.config["lone_pair_keep_angle_atom_pair_1"]
|
|
125
|
+
self.config["lone_pair_keep_angle_atom_pair_2"]
|
|
126
|
+
"""
|
|
127
|
+
lone_pair_1_vec_1 = (geom_num_list[self.config["lone_pair_keep_angle_atom_pair_1"][1]-1] - geom_num_list[self.config["lone_pair_keep_angle_atom_pair_1"][0]-1]) / self.bohr2angstroms
|
|
128
|
+
|
|
129
|
+
lone_pair_1_vec_2 = (geom_num_list[self.config["lone_pair_keep_angle_atom_pair_1"][2]-1] - geom_num_list[self.config["lone_pair_keep_angle_atom_pair_1"][0]-1]) / self.bohr2angstroms
|
|
130
|
+
lone_pair_1_vec_3 = (geom_num_list[self.config["lone_pair_keep_angle_atom_pair_1"][3]-1] - geom_num_list[self.config["lone_pair_keep_angle_atom_pair_1"][0]-1]) / self.bohr2angstroms
|
|
131
|
+
lone_pair_1_vector = (lone_pair_1_vec_1/torch.linalg.norm(lone_pair_1_vec_1)) + (lone_pair_1_vec_2/torch.linalg.norm(lone_pair_1_vec_2)) + (lone_pair_1_vec_3/torch.linalg.norm(lone_pair_1_vec_3))
|
|
132
|
+
|
|
133
|
+
lone_pair_1_vector_norm = lone_pair_1_vector / torch.linalg.norm(lone_pair_1_vector)
|
|
134
|
+
|
|
135
|
+
lone_pair_2_vec_1 = (geom_num_list[self.config["lone_pair_keep_angle_atom_pair_2"][1]-1] - geom_num_list[self.config["lone_pair_keep_angle_atom_pair_2"][0]-1]) / self.bohr2angstroms
|
|
136
|
+
lone_pair_2_vec_2 = (geom_num_list[self.config["lone_pair_keep_angle_atom_pair_2"][2]-1] - geom_num_list[self.config["lone_pair_keep_angle_atom_pair_2"][0]-1]) / self.bohr2angstroms
|
|
137
|
+
lone_pair_2_vec_3 = (geom_num_list[self.config["lone_pair_keep_angle_atom_pair_2"][3]-1] - geom_num_list[self.config["lone_pair_keep_angle_atom_pair_2"][0]-1]) / self.bohr2angstroms
|
|
138
|
+
|
|
139
|
+
lone_pair_2_vector = (lone_pair_2_vec_1/torch.linalg.norm(lone_pair_2_vec_1)) + (lone_pair_2_vec_2/torch.linalg.norm(lone_pair_2_vec_2)) + (lone_pair_2_vec_3/torch.linalg.norm(lone_pair_2_vec_3))
|
|
140
|
+
|
|
141
|
+
lone_pair_2_vector_norm = lone_pair_2_vector / torch.linalg.norm(lone_pair_2_vector)
|
|
142
|
+
|
|
143
|
+
theta = torch_calc_angle_from_vec(lone_pair_1_vector_norm, lone_pair_2_vector_norm)
|
|
144
|
+
energy = 0.5 * self.config["lone_pair_keep_angle_spring_const"] * (theta - torch.deg2rad(torch.tensor(self.config["lone_pair_keep_angle_angle"]))) ** 2
|
|
145
|
+
|
|
146
|
+
return energy
|