MultiOptPy 1.20.7__py3-none-any.whl → 1.20.8__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/ModelHessian/approx_hessian.py +4 -0
- multioptpy/ModelHessian/fischerd3old.py +381 -0
- multioptpy/Potential/keep_angle_potential.py +454 -42
- multioptpy/Potential/keep_dihedral_angle_potential.py +258 -47
- multioptpy/Potential/keep_outofplain_angle_potential.py +242 -35
- multioptpy/Potential/keep_potential.py +117 -38
- {multioptpy-1.20.7.dist-info → multioptpy-1.20.8.dist-info}/METADATA +7 -7
- {multioptpy-1.20.7.dist-info → multioptpy-1.20.8.dist-info}/RECORD +12 -11
- {multioptpy-1.20.7.dist-info → multioptpy-1.20.8.dist-info}/WHEEL +0 -0
- {multioptpy-1.20.7.dist-info → multioptpy-1.20.8.dist-info}/entry_points.txt +0 -0
- {multioptpy-1.20.7.dist-info → multioptpy-1.20.8.dist-info}/licenses/LICENSE +0 -0
- {multioptpy-1.20.7.dist-info → multioptpy-1.20.8.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@ import numpy as np
|
|
|
2
2
|
|
|
3
3
|
from multioptpy.ModelHessian.fischer import FischerApproxHessian
|
|
4
4
|
from multioptpy.ModelHessian.fischerd3 import FischerD3ApproxHessian
|
|
5
|
+
from multioptpy.ModelHessian.fischerd3old import FischerD3ApproxHessianOld
|
|
5
6
|
from multioptpy.ModelHessian.fischerd4 import FischerD4ApproxHessian
|
|
6
7
|
from multioptpy.ModelHessian.gfnff import GFNFFApproxHessian
|
|
7
8
|
from multioptpy.ModelHessian.gfn0xtb import GFN0XTBApproxHessian
|
|
@@ -35,6 +36,9 @@ class ApproxHessian:
|
|
|
35
36
|
elif "gfn0xtb" in approx_hess_type.lower():
|
|
36
37
|
GFN0AH = GFN0XTBApproxHessian()
|
|
37
38
|
hess_proj = GFN0AH.main(coord, element_list, cart_gradient)
|
|
39
|
+
elif "fischerd3old" in approx_hess_type.lower():
|
|
40
|
+
FAHD3O = FischerD3ApproxHessianOld()
|
|
41
|
+
hess_proj = FAHD3O.main(coord, element_list, cart_gradient)
|
|
38
42
|
elif "fischerd3" in approx_hess_type.lower():
|
|
39
43
|
FAHD3 = FischerD3ApproxHessian()
|
|
40
44
|
hess_proj = FAHD3.main(coord, element_list, cart_gradient)
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.spatial.distance import cdist # Highly recommended for vectorized distance calculation
|
|
3
|
+
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib
|
|
4
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
5
|
+
from multioptpy.Parameters.parameter import D3Parameters, D2_C6_coeff_lib, D2_VDW_radii_lib
|
|
6
|
+
from multioptpy.Utils.bond_connectivity import BondConnectivity
|
|
7
|
+
from multioptpy.ModelHessian.calc_params import torsion2, stretch2, bend2
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FischerD3ApproxHessianOld:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""
|
|
13
|
+
Fischer's Model Hessian implementation with D3 dispersion correction
|
|
14
|
+
Ref: Fischer and Almlöf, J. Phys. Chem., 1992, 96, 24, 9768–9774
|
|
15
|
+
Implementation Ref.: pysisyphus.optimizers.guess_hessians
|
|
16
|
+
"""
|
|
17
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
18
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
19
|
+
self.bond_factor = 1.3 # Bond detection threshold factor
|
|
20
|
+
|
|
21
|
+
# D3 dispersion correction parameters (default: PBE0)
|
|
22
|
+
self.d3_params = D3Parameters()
|
|
23
|
+
self.cart_hess = None
|
|
24
|
+
|
|
25
|
+
def calc_bond_force_const(self, r_ab, r_ab_cov):
|
|
26
|
+
"""Calculate force constant for bond stretching using Fischer formula"""
|
|
27
|
+
return 0.3601 * np.exp(-1.944 * (r_ab - r_ab_cov))
|
|
28
|
+
|
|
29
|
+
def calc_bend_force_const(self, r_ab, r_ac, r_ab_cov, r_ac_cov):
|
|
30
|
+
"""Calculate force constant for angle bending"""
|
|
31
|
+
val = r_ab_cov * r_ac_cov
|
|
32
|
+
if abs(val) < 1.0e-10:
|
|
33
|
+
return 0.0
|
|
34
|
+
|
|
35
|
+
return 0.089 + 0.11 / (val) ** (-0.42) * np.exp(
|
|
36
|
+
-0.44 * (r_ab + r_ac - r_ab_cov - r_ac_cov)
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def calc_dihedral_force_const(self, r_ab, r_ab_cov, bond_sum):
|
|
40
|
+
"""Calculate force constant for dihedral torsion"""
|
|
41
|
+
val = r_ab * r_ab_cov
|
|
42
|
+
if abs(val) < 1.0e-10:
|
|
43
|
+
return 0.0
|
|
44
|
+
return 0.0015 + 14.0 * max(bond_sum, 0) ** 0.57 / (val) ** 4.0 * np.exp(
|
|
45
|
+
-2.85 * (r_ab - r_ab_cov)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def get_c6_coefficient(self, element_i, element_j):
|
|
49
|
+
"""Get C6 coefficient based on D3 model (simplified)"""
|
|
50
|
+
c6_i = D2_C6_coeff_lib(element_i)
|
|
51
|
+
c6_j = D2_C6_coeff_lib(element_j)
|
|
52
|
+
c6_ij = np.sqrt(c6_i * c6_j)
|
|
53
|
+
return c6_ij
|
|
54
|
+
|
|
55
|
+
def get_c8_coefficient(self, element_i, element_j):
|
|
56
|
+
"""Calculate C8 coefficient based on D3 model using reference r4r2 values"""
|
|
57
|
+
c6_ij = self.get_c6_coefficient(element_i, element_j)
|
|
58
|
+
r4r2_i = self.d3_params.get_r4r2(element_i)
|
|
59
|
+
r4r2_j = self.d3_params.get_r4r2(element_j)
|
|
60
|
+
c8_ij = 3.0 * c6_ij * np.sqrt(r4r2_i * r4r2_j)
|
|
61
|
+
return c8_ij
|
|
62
|
+
|
|
63
|
+
def get_r0_value(self, element_i, element_j):
|
|
64
|
+
"""Calculate R0 value for D3 model (characteristic distance for atom pair)"""
|
|
65
|
+
try:
|
|
66
|
+
r_i = D2_VDW_radii_lib(element_i)
|
|
67
|
+
r_j = D2_VDW_radii_lib(element_j)
|
|
68
|
+
return r_i + r_j
|
|
69
|
+
except:
|
|
70
|
+
r_i = covalent_radii_lib(element_i) * 1.5
|
|
71
|
+
r_j = covalent_radii_lib(element_j) * 1.5
|
|
72
|
+
return r_i + r_j
|
|
73
|
+
|
|
74
|
+
def d3_damping_function(self, r_ij, r0, order=6):
|
|
75
|
+
"""BJ (Becke-Johnson) damping function for D3"""
|
|
76
|
+
if order == 6:
|
|
77
|
+
a1, a2 = self.d3_params.a1, self.d3_params.a2
|
|
78
|
+
else:
|
|
79
|
+
a1, a2 = self.d3_params.a1, self.d3_params.a2 + 2.0
|
|
80
|
+
|
|
81
|
+
denominator = r_ij**order + (a1 * r0 + a2)**order
|
|
82
|
+
return r_ij**order / denominator
|
|
83
|
+
|
|
84
|
+
def d3_hessian_contribution(self, r_vec, r_ij, element_i, element_j):
|
|
85
|
+
"""Calculate D3 dispersion contribution to Hessian"""
|
|
86
|
+
if r_ij < 0.1:
|
|
87
|
+
return np.zeros((3, 3))
|
|
88
|
+
|
|
89
|
+
c6_ij = self.get_c6_coefficient(element_i, element_j)
|
|
90
|
+
c8_ij = self.get_c8_coefficient(element_i, element_j)
|
|
91
|
+
r0 = self.get_r0_value(element_i, element_j)
|
|
92
|
+
|
|
93
|
+
f_damp6 = self.d3_damping_function(r_ij, r0, order=6)
|
|
94
|
+
f_damp8 = self.d3_damping_function(r_ij, r0, order=8)
|
|
95
|
+
|
|
96
|
+
# Derivatives of damping functions
|
|
97
|
+
a1, a2 = self.d3_params.a1, self.d3_params.a2
|
|
98
|
+
a1_8, a2_8 = self.d3_params.a1, self.d3_params.a2 + 2.0
|
|
99
|
+
|
|
100
|
+
denom6 = r_ij**6 + (a1 * r0 + a2)**6
|
|
101
|
+
denom8 = r_ij**8 + (a1_8 * r0 + a2_8)**8
|
|
102
|
+
|
|
103
|
+
# df_damp/dr
|
|
104
|
+
df_damp6 = 6 * r_ij**5 / denom6 - 6 * r_ij**12 / denom6**2
|
|
105
|
+
df_damp8 = 8 * r_ij**7 / denom8 - 8 * r_ij**16 / denom8**2
|
|
106
|
+
|
|
107
|
+
# dE/dr (Gradient magnitude)
|
|
108
|
+
g6 = -self.d3_params.s6 * c6_ij * ((-6.0 / r_ij**7) * f_damp6 + (1.0 / r_ij**6) * df_damp6)
|
|
109
|
+
g8 = -self.d3_params.s8 * c8_ij * ((-8.0 / r_ij**9) * f_damp8 + (1.0 / r_ij**8) * df_damp8)
|
|
110
|
+
|
|
111
|
+
# Unit vector and projection operator
|
|
112
|
+
unit_vec = r_vec / r_ij
|
|
113
|
+
proj_op = np.outer(unit_vec, unit_vec) # P = r_hat * r_hat^T
|
|
114
|
+
|
|
115
|
+
# Coefficients for H = (d^2E/dr^2) * P + (1/r * dE/dr) * (I - P)
|
|
116
|
+
# Using the simplified structure from the original code for d^2E/dr^2 approximation:
|
|
117
|
+
h6_proj_coeff = self.d3_params.s6 * c6_ij / r_ij**8 * (42.0 * f_damp6 - r_ij * df_damp6)
|
|
118
|
+
h8_proj_coeff = self.d3_params.s8 * c8_ij / r_ij**10 * (72.0 * f_damp8 - r_ij * df_damp8)
|
|
119
|
+
|
|
120
|
+
h_proj = h6_proj_coeff + h8_proj_coeff # d^2E/dr^2 approximation
|
|
121
|
+
h_perp = (g6 + g8) / r_ij # 1/r * dE/dr (Perpendicular coefficient)
|
|
122
|
+
|
|
123
|
+
# Construct Hessian matrix
|
|
124
|
+
identity = np.eye(3)
|
|
125
|
+
hessian = h_proj * proj_op + h_perp * (identity - proj_op)
|
|
126
|
+
|
|
127
|
+
return hessian
|
|
128
|
+
|
|
129
|
+
# --- Optimized: Vectorized connectivity calculation ---
|
|
130
|
+
def get_bond_connectivity(self, coord, element_list):
|
|
131
|
+
"""Calculate bond connectivity matrix and related data (Optimized with vectorization)"""
|
|
132
|
+
n_atoms = len(coord)
|
|
133
|
+
|
|
134
|
+
# 1. Distance matrix (Vectorized)
|
|
135
|
+
try:
|
|
136
|
+
dist_mat = cdist(coord, coord)
|
|
137
|
+
except NameError:
|
|
138
|
+
diff = coord[:, None, :] - coord[None, :, :]
|
|
139
|
+
dist_mat = np.linalg.norm(diff, axis=-1)
|
|
140
|
+
|
|
141
|
+
# 2. Covalent radii sums (Vectorized)
|
|
142
|
+
cov_radii = np.array([covalent_radii_lib(e) for e in element_list])
|
|
143
|
+
pair_cov_radii_mat = cov_radii[:, None] + cov_radii[None, :]
|
|
144
|
+
|
|
145
|
+
# 3. Bond connectivity matrix
|
|
146
|
+
bond_mat = dist_mat <= (pair_cov_radii_mat * self.bond_factor)
|
|
147
|
+
np.fill_diagonal(bond_mat, False)
|
|
148
|
+
|
|
149
|
+
return bond_mat, dist_mat, pair_cov_radii_mat
|
|
150
|
+
|
|
151
|
+
# --- Optimized: Block assignment using slicing ---
|
|
152
|
+
def fischer_bond(self, coord, element_list):
|
|
153
|
+
"""Calculate Hessian components for bond stretching (Optimized with slicing)"""
|
|
154
|
+
BC = BondConnectivity()
|
|
155
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
156
|
+
bond_indices = BC.bond_connect_table(b_c_mat)
|
|
157
|
+
|
|
158
|
+
for idx in bond_indices:
|
|
159
|
+
i, j = idx
|
|
160
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
161
|
+
r_ij_cov = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
162
|
+
|
|
163
|
+
force_const = self.calc_bond_force_const(r_ij, r_ij_cov)
|
|
164
|
+
|
|
165
|
+
t_xyz = np.array([coord[i], coord[j]])
|
|
166
|
+
r, b_vec = stretch2(t_xyz)
|
|
167
|
+
|
|
168
|
+
# Optimized: Use NumPy slicing and outer product
|
|
169
|
+
b_vec_i, b_vec_j = b_vec[0], b_vec[1]
|
|
170
|
+
|
|
171
|
+
H_ii_block = force_const * np.outer(b_vec_i, b_vec_i)
|
|
172
|
+
H_jj_block = force_const * np.outer(b_vec_j, b_vec_j)
|
|
173
|
+
H_ij_block = force_const * np.outer(b_vec_i, b_vec_j)
|
|
174
|
+
H_ji_block = force_const * np.outer(b_vec_j, b_vec_i)
|
|
175
|
+
|
|
176
|
+
start_i, end_i = 3 * i, 3 * i + 3
|
|
177
|
+
start_j, end_j = 3 * j, 3 * j + 3
|
|
178
|
+
|
|
179
|
+
self.cart_hess[start_i:end_i, start_i:end_i] += H_ii_block
|
|
180
|
+
self.cart_hess[start_j:end_j, start_j:end_j] += H_jj_block
|
|
181
|
+
self.cart_hess[start_i:end_i, start_j:end_j] += H_ij_block
|
|
182
|
+
self.cart_hess[start_j:end_j, start_i:end_i] += H_ji_block
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def fischer_angle(self, coord, element_list):
|
|
186
|
+
"""Calculate Hessian components for angle bending (Optimized with slicing)"""
|
|
187
|
+
BC = BondConnectivity()
|
|
188
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
189
|
+
angle_indices = BC.angle_connect_table(b_c_mat)
|
|
190
|
+
|
|
191
|
+
for idx in angle_indices:
|
|
192
|
+
i, j, k = idx # i-j-k angle
|
|
193
|
+
|
|
194
|
+
# --- Linear molecule handling: Check for linearity before processing ---
|
|
195
|
+
r_ij_vec = coord[i] - coord[j]
|
|
196
|
+
r_jk_vec = coord[k] - coord[j]
|
|
197
|
+
|
|
198
|
+
r_ij = np.linalg.norm(r_ij_vec)
|
|
199
|
+
r_jk = np.linalg.norm(r_jk_vec)
|
|
200
|
+
|
|
201
|
+
# Skip if atoms overlap
|
|
202
|
+
if r_ij < 0.1 or r_jk < 0.1:
|
|
203
|
+
continue
|
|
204
|
+
|
|
205
|
+
# Check for linearity (180 deg) or overlap (0 deg)
|
|
206
|
+
cos_theta = np.dot(r_ij_vec, r_jk_vec) / (r_ij * r_jk)
|
|
207
|
+
if abs(cos_theta) > 0.9999:
|
|
208
|
+
continue
|
|
209
|
+
# ----------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
r_ij_cov = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
212
|
+
r_jk_cov = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
213
|
+
|
|
214
|
+
force_const = self.calc_bend_force_const(r_ij, r_jk, r_ij_cov, r_jk_cov)
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
t_xyz = np.array([coord[i], coord[j], coord[k]])
|
|
218
|
+
theta, b_vec = bend2(t_xyz)
|
|
219
|
+
|
|
220
|
+
# Optimized: Use NumPy slicing and outer product
|
|
221
|
+
atoms = [i, j, k]
|
|
222
|
+
|
|
223
|
+
for m_idx, m_atom in enumerate(atoms):
|
|
224
|
+
for n_idx, n_atom in enumerate(atoms):
|
|
225
|
+
start_m, end_m = 3 * m_atom, 3 * m_atom + 3
|
|
226
|
+
start_n, end_n = 3 * n_atom, 3 * n_atom + 3
|
|
227
|
+
|
|
228
|
+
H_mn_block = force_const * np.outer(b_vec[m_idx], b_vec[n_idx])
|
|
229
|
+
self.cart_hess[start_m:end_m, start_n:end_n] += H_mn_block
|
|
230
|
+
except Exception:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def fischer_dihedral(self, coord, element_list, bond_mat):
|
|
235
|
+
"""Calculate Hessian components for dihedral torsion (Optimized with singularity damping)"""
|
|
236
|
+
BC = BondConnectivity()
|
|
237
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
238
|
+
dihedral_indices = BC.dihedral_angle_connect_table(b_c_mat)
|
|
239
|
+
|
|
240
|
+
# Calculate bond count for central atoms in dihedrals
|
|
241
|
+
tors_atom_bonds = {}
|
|
242
|
+
for idx in dihedral_indices:
|
|
243
|
+
i, j, k, l = idx # i-j-k-l dihedral
|
|
244
|
+
bond_sum = bond_mat[j].sum() + bond_mat[k].sum() - 2
|
|
245
|
+
tors_atom_bonds[(j, k)] = bond_sum
|
|
246
|
+
|
|
247
|
+
for idx in dihedral_indices:
|
|
248
|
+
i, j, k, l = idx
|
|
249
|
+
|
|
250
|
+
# Vector calculations
|
|
251
|
+
vec_ji = coord[i] - coord[j]
|
|
252
|
+
vec_jk = coord[k] - coord[j]
|
|
253
|
+
vec_kl = coord[l] - coord[k]
|
|
254
|
+
|
|
255
|
+
r_jk = np.linalg.norm(vec_jk)
|
|
256
|
+
r_jk_cov = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
257
|
+
|
|
258
|
+
bond_sum = tors_atom_bonds.get((j, k), 0)
|
|
259
|
+
|
|
260
|
+
# Calculate base force constant
|
|
261
|
+
force_const = self.calc_dihedral_force_const(r_jk, r_jk_cov, bond_sum)
|
|
262
|
+
|
|
263
|
+
# --- Singularity Handling (Damping for linear angles) ---
|
|
264
|
+
# Determine linearity of angles i-j-k and j-k-l
|
|
265
|
+
|
|
266
|
+
# Angle 1: i-j-k
|
|
267
|
+
n_ji = np.linalg.norm(vec_ji)
|
|
268
|
+
n_jk = r_jk # already calculated
|
|
269
|
+
|
|
270
|
+
# Avoid division by zero if atoms overlap
|
|
271
|
+
if n_ji < 1e-8 or n_jk < 1e-8:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
cos_theta1 = np.dot(vec_ji, vec_jk) / (n_ji * n_jk)
|
|
275
|
+
|
|
276
|
+
# Angle 2: j-k-l (Note: vec_kj is -vec_jk)
|
|
277
|
+
vec_kj = -vec_jk
|
|
278
|
+
n_kl = np.linalg.norm(vec_kl)
|
|
279
|
+
|
|
280
|
+
if n_kl < 1e-8:
|
|
281
|
+
continue
|
|
282
|
+
|
|
283
|
+
cos_theta2 = np.dot(vec_kj, vec_kl) / (n_jk * n_kl)
|
|
284
|
+
|
|
285
|
+
# --- Damping Factor Calculation ---
|
|
286
|
+
# The Wilson B-matrix contains 1/sin(theta) terms which diverge at 180 deg.
|
|
287
|
+
# We scale the force constant by sin^2(theta) to cancel this divergence.
|
|
288
|
+
# sin^2(theta) = 1 - cos^2(theta)
|
|
289
|
+
|
|
290
|
+
sin2_theta1 = 1.0 - min(cos_theta1**2, 1.0)
|
|
291
|
+
sin2_theta2 = 1.0 - min(cos_theta2**2, 1.0)
|
|
292
|
+
|
|
293
|
+
# Hard cutoff: If geometry is extremely linear, skip to avoid NaN
|
|
294
|
+
if sin2_theta1 < 1e-4 or sin2_theta2 < 1e-4:
|
|
295
|
+
continue
|
|
296
|
+
|
|
297
|
+
# Apply scaling factor
|
|
298
|
+
# This ensures the force constant goes to 0 as the angle becomes linear
|
|
299
|
+
scaling_factor = sin2_theta1 * sin2_theta2
|
|
300
|
+
force_const *= scaling_factor
|
|
301
|
+
|
|
302
|
+
# --------------------------------------------------------
|
|
303
|
+
|
|
304
|
+
t_xyz = np.array([coord[i], coord[j], coord[k], coord[l]])
|
|
305
|
+
|
|
306
|
+
try:
|
|
307
|
+
tau, b_vec = torsion2(t_xyz)
|
|
308
|
+
except (ValueError, ArithmeticError):
|
|
309
|
+
# Skip if numerical errors occur in torsion calculation
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
# Optimized: Use NumPy slicing and outer product
|
|
313
|
+
atoms = [i, j, k, l]
|
|
314
|
+
|
|
315
|
+
for m_idx, m_atom in enumerate(atoms):
|
|
316
|
+
for n_idx, n_atom in enumerate(atoms):
|
|
317
|
+
start_m, end_m = 3 * m_atom, 3 * m_atom + 3
|
|
318
|
+
start_n, end_n = 3 * n_atom, 3 * n_atom + 3
|
|
319
|
+
|
|
320
|
+
H_mn_block = force_const * np.outer(b_vec[m_idx], b_vec[n_idx])
|
|
321
|
+
self.cart_hess[start_m:end_m, start_n:end_n] += H_mn_block
|
|
322
|
+
|
|
323
|
+
# --- Optimized: Block assignment using slicing (and fixed logic) ---
|
|
324
|
+
def d3_dispersion_hessian(self, coord, element_list, bond_mat):
|
|
325
|
+
"""Calculate Hessian correction based on D3 dispersion forces (Optimized/Corrected)"""
|
|
326
|
+
n_atoms = len(coord)
|
|
327
|
+
|
|
328
|
+
# Calculate D3 dispersion correction for all atom pairs (i > j)
|
|
329
|
+
for i in range(n_atoms):
|
|
330
|
+
for j in range(i):
|
|
331
|
+
# Skip bonded atom pairs
|
|
332
|
+
if bond_mat[i, j]:
|
|
333
|
+
continue
|
|
334
|
+
|
|
335
|
+
r_vec = coord[i] - coord[j]
|
|
336
|
+
r_ij = np.linalg.norm(r_vec)
|
|
337
|
+
|
|
338
|
+
if r_ij < 0.1:
|
|
339
|
+
continue
|
|
340
|
+
|
|
341
|
+
# Calculate D3 Hessian contribution (3x3 block)
|
|
342
|
+
hess_block = self.d3_hessian_contribution(r_vec, r_ij, element_list[i], element_list[j])
|
|
343
|
+
|
|
344
|
+
# Use slicing for efficient block assignment
|
|
345
|
+
# H_ii += H_block, H_jj += H_block, H_ij -= H_block, H_ji -= H_block
|
|
346
|
+
start_i, end_i = 3 * i, 3 * i + 3
|
|
347
|
+
start_j, end_j = 3 * j, 3 * j + 3
|
|
348
|
+
|
|
349
|
+
self.cart_hess[start_i:end_i, start_i:end_i] += hess_block
|
|
350
|
+
self.cart_hess[start_j:end_j, start_j:end_j] += hess_block
|
|
351
|
+
self.cart_hess[start_i:end_i, start_j:end_j] -= hess_block
|
|
352
|
+
self.cart_hess[start_j:end_j, start_i:end_i] -= hess_block
|
|
353
|
+
|
|
354
|
+
# --- Optimized: Main function flow ---
|
|
355
|
+
def main(self, coord, element_list, cart_gradient):
|
|
356
|
+
"""
|
|
357
|
+
Calculate Hessian combining Fischer model and D3 dispersion correction
|
|
358
|
+
"""
|
|
359
|
+
print("Generating Hessian using Fischer model using D3 dispersion parameter...")
|
|
360
|
+
|
|
361
|
+
n_atoms = len(coord)
|
|
362
|
+
self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
|
|
363
|
+
|
|
364
|
+
# Calculate bond connectivity matrix ONCE (Optimized internally)
|
|
365
|
+
bond_mat, dist_mat, pair_cov_radii_mat = self.get_bond_connectivity(coord, element_list)
|
|
366
|
+
|
|
367
|
+
# Calculate Hessian components from Fischer model (Optimized internally with slicing)
|
|
368
|
+
self.fischer_bond(coord, element_list)
|
|
369
|
+
self.fischer_angle(coord, element_list)
|
|
370
|
+
self.fischer_dihedral(coord, element_list, bond_mat)
|
|
371
|
+
|
|
372
|
+
# Calculate Hessian components from D3 dispersion correction (Optimized internally with slicing)
|
|
373
|
+
self.d3_dispersion_hessian(coord, element_list, bond_mat)
|
|
374
|
+
|
|
375
|
+
# Optimized: Symmetrize the Hessian matrix
|
|
376
|
+
self.cart_hess = (self.cart_hess + self.cart_hess.T) / 2.0
|
|
377
|
+
|
|
378
|
+
# Project out rotational and translational modes
|
|
379
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
|
|
380
|
+
|
|
381
|
+
return hess_proj
|