MultiOptPy 1.20.4__py3-none-any.whl → 1.20.6__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/ase_calculation_tools.py +21 -8
- multioptpy/Calculator/ase_tools/gxtb_dev.py +41 -0
- multioptpy/Calculator/ase_tools/orca.py +228 -14
- multioptpy/MD/thermostat.py +236 -123
- multioptpy/ModelHessian/fischerd3.py +240 -295
- multioptpy/Optimizer/rsirfo.py +112 -4
- multioptpy/Optimizer/rsprfo.py +1005 -698
- multioptpy/OtherMethod/spring_pair_method.py +314 -0
- multioptpy/entrypoints.py +406 -16
- multioptpy/ieip.py +14 -2
- multioptpy/interface.py +3 -1
- multioptpy/moleculardynamics.py +21 -13
- {multioptpy-1.20.4.dist-info → multioptpy-1.20.6.dist-info}/METADATA +20 -20
- {multioptpy-1.20.4.dist-info → multioptpy-1.20.6.dist-info}/RECORD +18 -16
- {multioptpy-1.20.4.dist-info → multioptpy-1.20.6.dist-info}/WHEEL +1 -1
- {multioptpy-1.20.4.dist-info → multioptpy-1.20.6.dist-info}/entry_points.txt +0 -0
- {multioptpy-1.20.4.dist-info → multioptpy-1.20.6.dist-info}/licenses/LICENSE +0 -0
- {multioptpy-1.20.4.dist-info → multioptpy-1.20.6.dist-info}/top_level.txt +0 -0
|
@@ -1,360 +1,305 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from scipy.spatial.distance import cdist
|
|
2
|
+
from scipy.spatial.distance import cdist
|
|
3
3
|
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib
|
|
4
4
|
from multioptpy.Utils.calc_tools import Calculationtools
|
|
5
5
|
from multioptpy.Parameters.parameter import D3Parameters, D2_C6_coeff_lib, D2_VDW_radii_lib
|
|
6
6
|
from multioptpy.Utils.bond_connectivity import BondConnectivity
|
|
7
|
-
from multioptpy.ModelHessian.calc_params import torsion2,
|
|
8
|
-
|
|
7
|
+
from multioptpy.ModelHessian.calc_params import torsion2, bend2
|
|
9
8
|
|
|
10
9
|
class FischerD3ApproxHessian:
|
|
11
10
|
def __init__(self):
|
|
12
11
|
"""
|
|
13
|
-
Fischer's Model Hessian implementation with D3 dispersion correction
|
|
14
|
-
|
|
15
|
-
Implementation Ref.: pysisyphus.optimizers.guess_hessians
|
|
12
|
+
Fischer's Model Hessian implementation with Dynamic D3 dispersion correction.
|
|
13
|
+
Robust against linear molecules and singularities.
|
|
16
14
|
"""
|
|
17
15
|
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
18
|
-
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
19
|
-
self.bond_factor = 1.3 # Bond detection threshold factor
|
|
20
16
|
|
|
21
|
-
# D3 dispersion correction parameters
|
|
17
|
+
# D3 dispersion correction parameters
|
|
22
18
|
self.d3_params = D3Parameters()
|
|
23
19
|
self.cart_hess = None
|
|
24
20
|
|
|
21
|
+
# Grimme D3 scaling factors for CN calculation
|
|
22
|
+
self.k1 = 16.0
|
|
23
|
+
self.k2 = 4.0 / 3.0
|
|
24
|
+
|
|
25
|
+
# Expanded Reference Coordination Numbers (Default averages)
|
|
26
|
+
# Covers H through Xe roughly. Values are heuristic/typical valencies or coordination.
|
|
27
|
+
self.ref_cn_map = {
|
|
28
|
+
# Period 1
|
|
29
|
+
'H': 1, 'He': 0,
|
|
30
|
+
# Period 2
|
|
31
|
+
'Li': 4, 'Be': 4, 'B': 3, 'C': 4, 'N': 3, 'O': 2, 'F': 1, 'Ne': 0,
|
|
32
|
+
# Period 3
|
|
33
|
+
'Na': 6, 'Mg': 6, 'Al': 6, 'Si': 4, 'P': 5, 'S': 6, 'Cl': 1, 'Ar': 0,
|
|
34
|
+
# Period 4 (Transition Metals set to ~6-12 depending on typical packing/complexes)
|
|
35
|
+
'K': 8, 'Ca': 6,
|
|
36
|
+
'Sc': 12, 'Ti': 12, 'V': 12, 'Cr': 6, 'Mn': 6, 'Fe': 6, 'Co': 6, 'Ni': 4, 'Cu': 4, 'Zn': 4,
|
|
37
|
+
'Ga': 4, 'Ge': 4, 'As': 3, 'Se': 2, 'Br': 1, 'Kr': 0,
|
|
38
|
+
# Period 5
|
|
39
|
+
'Rb': 8, 'Sr': 6,
|
|
40
|
+
'Y': 12, 'Zr': 12, 'Nb': 12, 'Mo': 6, 'Tc': 6, 'Ru': 6, 'Rh': 6, 'Pd': 4, 'Ag': 4, 'Cd': 4,
|
|
41
|
+
'In': 6, 'Sn': 4, 'Sb': 3, 'Te': 2, 'I': 1, 'Xe': 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
def calc_coordination_numbers(self, coord, element_list):
|
|
45
|
+
"""Calculate fractional coordination numbers (CN) for Dynamic D3."""
|
|
46
|
+
# Pre-fetch covalent radii
|
|
47
|
+
cov_r = np.array([covalent_radii_lib(e) for e in element_list])
|
|
48
|
+
|
|
49
|
+
# Vectorized distance matrix
|
|
50
|
+
r_mat = cdist(coord, coord)
|
|
51
|
+
np.fill_diagonal(r_mat, np.inf)
|
|
52
|
+
|
|
53
|
+
# rcov_sum[i, j] = cov_r[i] + cov_r[j]
|
|
54
|
+
rcov_sum = cov_r[:, None] + cov_r[None, :]
|
|
55
|
+
|
|
56
|
+
# D3 CN formula
|
|
57
|
+
term = -self.k1 * (self.k2 * (r_mat / rcov_sum) - 1.0)
|
|
58
|
+
term = np.clip(term, -100, 100) # Prevent overflow
|
|
59
|
+
cutoff_func = 1.0 / (1.0 + np.exp(term))
|
|
60
|
+
|
|
61
|
+
return np.sum(cutoff_func, axis=1)
|
|
62
|
+
|
|
63
|
+
# --- Fischer Terms (Robust) ---
|
|
64
|
+
|
|
25
65
|
def calc_bond_force_const(self, r_ab, r_ab_cov):
|
|
26
|
-
"""Calculate force constant for bond stretching using Fischer formula"""
|
|
27
66
|
return 0.3601 * np.exp(-1.944 * (r_ab - r_ab_cov))
|
|
28
67
|
|
|
29
68
|
def calc_bend_force_const(self, r_ab, r_ac, r_ab_cov, r_ac_cov):
|
|
30
|
-
"""Calculate force constant for angle bending"""
|
|
31
69
|
val = r_ab_cov * r_ac_cov
|
|
32
|
-
if abs(val) <
|
|
33
|
-
|
|
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
|
-
)
|
|
70
|
+
if abs(val) < 1e-10: return 0.0
|
|
71
|
+
return 0.089 + 0.11 / (val)**(-0.42) * np.exp(-0.44 * (r_ab + r_ac - r_ab_cov - r_ac_cov))
|
|
38
72
|
|
|
39
73
|
def calc_dihedral_force_const(self, r_ab, r_ab_cov, bond_sum):
|
|
40
|
-
"""Calculate force constant for dihedral torsion"""
|
|
41
74
|
val = r_ab * r_ab_cov
|
|
42
|
-
if abs(val) <
|
|
43
|
-
|
|
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
|
|
75
|
+
if abs(val) < 1e-10: return 0.0
|
|
76
|
+
return 0.0015 + 14.0 * max(bond_sum, 0)**0.57 / (val)**4.0 * np.exp(-2.85 * (r_ab - r_ab_cov))
|
|
83
77
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
78
|
+
def _add_block(self, i, j, block):
|
|
79
|
+
start_i, end_i = 3*i, 3*i+3
|
|
80
|
+
start_j, end_j = 3*j, 3*j+3
|
|
81
|
+
self.cart_hess[start_i:end_i, start_j:end_j] += block
|
|
128
82
|
|
|
129
|
-
|
|
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
|
-
|
|
83
|
+
def fischer_bond(self, coord, element_list, bond_indices):
|
|
158
84
|
for idx in bond_indices:
|
|
159
85
|
i, j = idx
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
r, b_vec = stretch2(t_xyz)
|
|
86
|
+
r_vec = coord[i] - coord[j]
|
|
87
|
+
r_ij = np.linalg.norm(r_vec)
|
|
88
|
+
if r_ij < 0.1: continue
|
|
89
|
+
|
|
90
|
+
r_cov = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
91
|
+
fc = self.calc_bond_force_const(r_ij, r_cov)
|
|
167
92
|
|
|
168
|
-
#
|
|
169
|
-
|
|
93
|
+
# Use safe unit vector calculation
|
|
94
|
+
u = r_vec / r_ij
|
|
95
|
+
block = fc * np.outer(u, u)
|
|
170
96
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
97
|
+
self._add_block(i, i, block)
|
|
98
|
+
self._add_block(j, j, block)
|
|
99
|
+
self._add_block(i, j, -block)
|
|
100
|
+
self._add_block(j, i, -block)
|
|
184
101
|
|
|
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
|
-
|
|
102
|
+
def fischer_angle(self, coord, element_list, angle_indices):
|
|
191
103
|
for idx in angle_indices:
|
|
192
|
-
i, j, k = idx
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
r_ij_cov = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
196
|
-
r_jk_cov = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
104
|
+
i, j, k = idx
|
|
105
|
+
r_ij_vec = coord[i] - coord[j]
|
|
106
|
+
r_jk_vec = coord[k] - coord[j] # Center is j
|
|
197
107
|
|
|
198
|
-
|
|
108
|
+
r_ij = np.linalg.norm(r_ij_vec)
|
|
109
|
+
r_jk = np.linalg.norm(r_jk_vec)
|
|
199
110
|
|
|
200
|
-
|
|
201
|
-
|
|
111
|
+
if r_ij < 0.1 or r_jk < 0.1:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
# Check for linearity (180 deg) or overlap (0 deg)
|
|
115
|
+
cos_theta = np.dot(r_ij_vec, r_jk_vec) / (r_ij * r_jk)
|
|
116
|
+
if abs(cos_theta) > 0.9999:
|
|
117
|
+
continue
|
|
118
|
+
|
|
119
|
+
r_cov_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
120
|
+
r_cov_jk = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
202
121
|
|
|
203
|
-
|
|
204
|
-
atoms = [i, j, k]
|
|
122
|
+
fc = self.calc_bend_force_const(r_ij, r_jk, r_cov_ij, r_cov_jk)
|
|
205
123
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
124
|
+
try:
|
|
125
|
+
# bend2 expects: [atom_end1, atom_center, atom_end2]
|
|
126
|
+
t_xyz = np.array([coord[i], coord[j], coord[k]])
|
|
127
|
+
_, b_vec = bend2(t_xyz)
|
|
128
|
+
|
|
129
|
+
atoms = [i, j, k]
|
|
130
|
+
for m_idx, m_atom in enumerate(atoms):
|
|
131
|
+
for n_idx, n_atom in enumerate(atoms):
|
|
132
|
+
block = fc * np.outer(b_vec[m_idx], b_vec[n_idx])
|
|
133
|
+
self._add_block(m_atom, n_atom, block)
|
|
134
|
+
except Exception:
|
|
135
|
+
continue
|
|
214
136
|
|
|
215
|
-
def fischer_dihedral(self, coord, element_list, bond_mat):
|
|
216
|
-
|
|
217
|
-
BC = BondConnectivity()
|
|
218
|
-
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
219
|
-
dihedral_indices = BC.dihedral_angle_connect_table(b_c_mat)
|
|
220
|
-
|
|
221
|
-
# Calculate bond count for central atoms in dihedrals
|
|
222
|
-
tors_atom_bonds = {}
|
|
223
|
-
for idx in dihedral_indices:
|
|
224
|
-
i, j, k, l = idx # i-j-k-l dihedral
|
|
225
|
-
bond_sum = bond_mat[j].sum() + bond_mat[k].sum() - 2
|
|
226
|
-
tors_atom_bonds[(j, k)] = bond_sum
|
|
137
|
+
def fischer_dihedral(self, coord, element_list, dihedral_indices, bond_mat):
|
|
138
|
+
neighbor_counts = np.sum(bond_mat, axis=1)
|
|
227
139
|
|
|
228
140
|
for idx in dihedral_indices:
|
|
229
141
|
i, j, k, l = idx
|
|
230
142
|
|
|
231
|
-
#
|
|
143
|
+
# Check linearity of the axis bond j-k and its connections
|
|
232
144
|
vec_ji = coord[i] - coord[j]
|
|
233
145
|
vec_jk = coord[k] - coord[j]
|
|
234
146
|
vec_kl = coord[l] - coord[k]
|
|
235
147
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
bond_sum = tors_atom_bonds.get((j, k), 0)
|
|
240
|
-
|
|
241
|
-
# Calculate base force constant
|
|
242
|
-
force_const = self.calc_dihedral_force_const(r_jk, r_jk_cov, bond_sum)
|
|
243
|
-
|
|
244
|
-
# --- Singularity Handling (Damping for linear angles) ---
|
|
245
|
-
# Determine linearity of angles i-j-k and j-k-l
|
|
246
|
-
|
|
247
|
-
# Angle 1: i-j-k
|
|
248
|
-
n_ji = np.linalg.norm(vec_ji)
|
|
249
|
-
n_jk = r_jk # already calculated
|
|
250
|
-
|
|
251
|
-
# Avoid division by zero if atoms overlap
|
|
252
|
-
if n_ji < 1e-8 or n_jk < 1e-8: continue
|
|
253
|
-
|
|
254
|
-
cos_theta1 = np.dot(vec_ji, vec_jk) / (n_ji * n_jk)
|
|
255
|
-
|
|
256
|
-
# Angle 2: j-k-l (Note: vec_kj is -vec_jk)
|
|
257
|
-
vec_kj = -vec_jk
|
|
258
|
-
n_kl = np.linalg.norm(vec_kl)
|
|
259
|
-
|
|
260
|
-
if n_kl < 1e-8: continue
|
|
261
|
-
|
|
262
|
-
cos_theta2 = np.dot(vec_kj, vec_kl) / (n_jk * n_kl)
|
|
148
|
+
n_ji, n_jk, n_kl = np.linalg.norm(vec_ji), np.linalg.norm(vec_jk), np.linalg.norm(vec_kl)
|
|
149
|
+
if min(n_ji, n_jk, n_kl) < 0.1: continue
|
|
263
150
|
|
|
264
|
-
#
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
# sin^2(theta) = 1 - cos^2(theta)
|
|
151
|
+
# Cosine check for linearity: i-j-k and j-k-l
|
|
152
|
+
cos1 = np.dot(vec_ji, vec_jk) / (n_ji * n_jk)
|
|
153
|
+
cos2 = np.dot(-vec_jk, vec_kl) / (n_jk * n_kl)
|
|
268
154
|
|
|
269
|
-
|
|
270
|
-
|
|
155
|
+
# Use sin^2 to dampen force constant as it approaches linearity
|
|
156
|
+
sin2_1 = 1.0 - min(cos1**2, 1.0)
|
|
157
|
+
sin2_2 = 1.0 - min(cos2**2, 1.0)
|
|
271
158
|
|
|
272
|
-
|
|
273
|
-
if sin2_theta1 < 1e-4 or sin2_theta2 < 1e-4:
|
|
159
|
+
if sin2_1 < 1e-3 or sin2_2 < 1e-3:
|
|
274
160
|
continue
|
|
275
161
|
|
|
276
|
-
#
|
|
277
|
-
|
|
278
|
-
scaling_factor = sin2_theta1 * sin2_theta2
|
|
279
|
-
force_const *= scaling_factor
|
|
280
|
-
|
|
281
|
-
# --------------------------------------------------------
|
|
282
|
-
|
|
283
|
-
t_xyz = np.array([coord[i], coord[j], coord[k], coord[l]])
|
|
162
|
+
# Damping factor
|
|
163
|
+
scaling = sin2_1 * sin2_2
|
|
284
164
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
except (ValueError, ArithmeticError):
|
|
288
|
-
# Skip if numerical errors occur in torsion calculation
|
|
289
|
-
continue
|
|
165
|
+
bond_sum = neighbor_counts[j] + neighbor_counts[k] - 2
|
|
166
|
+
r_cov_jk = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
290
167
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
for m_idx, m_atom in enumerate(atoms):
|
|
295
|
-
for n_idx, n_atom in enumerate(atoms):
|
|
296
|
-
start_m, end_m = 3 * m_atom, 3 * m_atom + 3
|
|
297
|
-
start_n, end_n = 3 * n_atom, 3 * n_atom + 3
|
|
298
|
-
|
|
299
|
-
H_mn_block = force_const * np.outer(b_vec[m_idx], b_vec[n_idx])
|
|
300
|
-
self.cart_hess[start_m:end_m, start_n:end_n] += H_mn_block
|
|
168
|
+
fc = self.calc_dihedral_force_const(n_jk, r_cov_jk, bond_sum)
|
|
169
|
+
fc *= scaling
|
|
301
170
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
n_atoms = len(coord)
|
|
306
|
-
|
|
307
|
-
# Calculate D3 dispersion correction for all atom pairs (i > j)
|
|
308
|
-
for i in range(n_atoms):
|
|
309
|
-
for j in range(i):
|
|
310
|
-
# Skip bonded atom pairs
|
|
311
|
-
if bond_mat[i, j]:
|
|
312
|
-
continue
|
|
313
|
-
|
|
314
|
-
r_vec = coord[i] - coord[j]
|
|
315
|
-
r_ij = np.linalg.norm(r_vec)
|
|
171
|
+
try:
|
|
172
|
+
t_xyz = np.array([coord[i], coord[j], coord[k], coord[l]])
|
|
173
|
+
_, b_vec = torsion2(t_xyz)
|
|
316
174
|
|
|
317
|
-
if
|
|
175
|
+
if not np.all(np.isfinite(b_vec)):
|
|
318
176
|
continue
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
self.cart_hess[start_i:end_i, start_i:end_i] += hess_block
|
|
329
|
-
self.cart_hess[start_j:end_j, start_j:end_j] += hess_block
|
|
330
|
-
self.cart_hess[start_i:end_i, start_j:end_j] -= hess_block
|
|
331
|
-
self.cart_hess[start_j:end_j, start_i:end_i] -= hess_block
|
|
332
|
-
|
|
333
|
-
# --- Optimized: Main function flow ---
|
|
177
|
+
|
|
178
|
+
atoms = [i, j, k, l]
|
|
179
|
+
for m_idx, m_atom in enumerate(atoms):
|
|
180
|
+
for n_idx, n_atom in enumerate(atoms):
|
|
181
|
+
block = fc * np.outer(b_vec[m_idx], b_vec[n_idx])
|
|
182
|
+
self._add_block(m_atom, n_atom, block)
|
|
183
|
+
except Exception:
|
|
184
|
+
continue
|
|
185
|
+
|
|
334
186
|
def main(self, coord, element_list, cart_gradient):
|
|
335
|
-
""
|
|
336
|
-
Calculate Hessian combining Fischer model and D3 dispersion correction
|
|
337
|
-
"""
|
|
338
|
-
print("Generating Hessian using Fischer model with D3 dispersion correction...")
|
|
187
|
+
print("Generating Hessian using Fischer model with Dynamic D3 correction (Robust)...")
|
|
339
188
|
|
|
340
189
|
n_atoms = len(coord)
|
|
341
190
|
self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
|
|
342
191
|
|
|
343
|
-
#
|
|
344
|
-
|
|
192
|
+
# 1. Topology
|
|
193
|
+
BC = BondConnectivity()
|
|
194
|
+
bond_mat = BC.bond_connect_matrix(element_list, coord)
|
|
195
|
+
bond_indices = BC.bond_connect_table(bond_mat)
|
|
196
|
+
angle_indices = BC.angle_connect_table(bond_mat)
|
|
197
|
+
dihedral_indices = BC.dihedral_angle_connect_table(bond_mat)
|
|
345
198
|
|
|
346
|
-
#
|
|
347
|
-
self.
|
|
348
|
-
self.fischer_angle(coord, element_list)
|
|
349
|
-
self.fischer_dihedral(coord, element_list, bond_mat)
|
|
199
|
+
# 2. Dynamic D3 Parameters (Coordination Numbers)
|
|
200
|
+
cn_list = self.calc_coordination_numbers(coord, element_list)
|
|
350
201
|
|
|
351
|
-
#
|
|
352
|
-
self.
|
|
202
|
+
# 3. Fischer Contributions (with strict linearity checks)
|
|
203
|
+
self.fischer_bond(coord, element_list, bond_indices)
|
|
204
|
+
self.fischer_angle(coord, element_list, angle_indices)
|
|
205
|
+
self.fischer_dihedral(coord, element_list, dihedral_indices, bond_mat)
|
|
353
206
|
|
|
354
|
-
#
|
|
355
|
-
|
|
207
|
+
# 4. Dynamic D3 Dispersion (Vectorized)
|
|
208
|
+
mask = np.tril(np.ones((n_atoms, n_atoms), dtype=bool), k=-1) & ~bond_mat
|
|
209
|
+
|
|
210
|
+
diff_tensor = coord[:, None, :] - coord[None, :, :] # (N, N, 3)
|
|
211
|
+
dist_matrix = np.linalg.norm(diff_tensor, axis=-1)
|
|
212
|
+
|
|
213
|
+
mask &= (dist_matrix > 0.1)
|
|
214
|
+
i_idx, j_idx = np.where(mask)
|
|
215
|
+
|
|
216
|
+
if len(i_idx) > 0:
|
|
217
|
+
# --- Pre-compute atomic parameters for all atoms (O(N)) ---
|
|
218
|
+
c6_atoms = np.array([D2_C6_coeff_lib(e) for e in element_list])
|
|
219
|
+
r4r2_atoms = np.array([self.d3_params.get_r4r2(e) for e in element_list])
|
|
220
|
+
ref_cn_atoms = np.array([self.ref_cn_map.get(e, 4) for e in element_list])
|
|
221
|
+
|
|
222
|
+
# Safe VDW radii retrieval
|
|
223
|
+
vdw_atoms = np.zeros(n_atoms)
|
|
224
|
+
for k, e in enumerate(element_list):
|
|
225
|
+
try: vdw_atoms[k] = D2_VDW_radii_lib(e)
|
|
226
|
+
except: vdw_atoms[k] = covalent_radii_lib(e) * 1.5
|
|
227
|
+
|
|
228
|
+
# --- Gather Pair Data (O(M)) ---
|
|
229
|
+
r_vecs = diff_tensor[i_idx, j_idx] # Vector r_ij (M, 3)
|
|
230
|
+
r_ijs = dist_matrix[i_idx, j_idx] # Distance scalar (M,)
|
|
231
|
+
|
|
232
|
+
# Dynamic C6 Scaling
|
|
233
|
+
cn_i = cn_list[i_idx]
|
|
234
|
+
cn_j = cn_list[j_idx]
|
|
235
|
+
scale_i = np.clip(1.0 - 0.05 * (cn_i - ref_cn_atoms[i_idx]), 0.75, 1.25)
|
|
236
|
+
scale_j = np.clip(1.0 - 0.05 * (cn_j - ref_cn_atoms[j_idx]), 0.75, 1.25)
|
|
237
|
+
|
|
238
|
+
c6_ijs = np.sqrt((c6_atoms[i_idx] * scale_i) * (c6_atoms[j_idx] * scale_j))
|
|
239
|
+
|
|
240
|
+
# C8 and R0
|
|
241
|
+
c8_ijs = 3.0 * c6_ijs * np.sqrt(r4r2_atoms[i_idx] * r4r2_atoms[j_idx])
|
|
242
|
+
r0s = vdw_atoms[i_idx] + vdw_atoms[j_idx]
|
|
243
|
+
|
|
244
|
+
# --- Vectorized Gradient & Hessian Calculation ---
|
|
245
|
+
a1 = self.d3_params.a1
|
|
246
|
+
a2_6 = self.d3_params.a2
|
|
247
|
+
a2_8 = self.d3_params.a2 + 2.0
|
|
248
|
+
|
|
249
|
+
# Damping terms
|
|
250
|
+
denom6 = r_ijs**6 + (a1 * r0s + a2_6)**6
|
|
251
|
+
denom8 = r_ijs**8 + (a1 * r0s + a2_8)**8
|
|
252
|
+
|
|
253
|
+
f_damp6 = r_ijs**6 / denom6
|
|
254
|
+
f_damp8 = r_ijs**8 / denom8
|
|
255
|
+
|
|
256
|
+
df_damp6 = 6 * r_ijs**5 / denom6 - 6 * r_ijs**12 / denom6**2
|
|
257
|
+
df_damp8 = 8 * r_ijs**7 / denom8 - 8 * r_ijs**16 / denom8**2
|
|
258
|
+
|
|
259
|
+
# Gradients (Forces)
|
|
260
|
+
g6 = -self.d3_params.s6 * c6_ijs * ((-6.0/r_ijs**7)*f_damp6 + (1.0/r_ijs**6)*df_damp6)
|
|
261
|
+
g8 = -self.d3_params.s8 * c8_ijs * ((-8.0/r_ijs**9)*f_damp8 + (1.0/r_ijs**8)*df_damp8)
|
|
262
|
+
|
|
263
|
+
# Second Derivatives (Projection coefficients)
|
|
264
|
+
h6_proj = self.d3_params.s6 * c6_ijs / r_ijs**8 * (42.0*f_damp6 - r_ijs*df_damp6)
|
|
265
|
+
h8_proj = self.d3_params.s8 * c8_ijs / r_ijs**10 * (72.0*f_damp8 - r_ijs*df_damp8)
|
|
266
|
+
|
|
267
|
+
h_proj_vals = h6_proj + h8_proj
|
|
268
|
+
h_perp_vals = (g6 + g8) / r_ijs
|
|
269
|
+
|
|
270
|
+
# Construct Hessian Blocks (M, 3, 3)
|
|
271
|
+
# P = u * u.T
|
|
272
|
+
unit_vecs = r_vecs / r_ijs[:, None]
|
|
273
|
+
proj_ops = np.einsum('bi,bj->bij', unit_vecs, unit_vecs) # (M, 3, 3)
|
|
274
|
+
identity = np.eye(3)[None, :, :]
|
|
275
|
+
|
|
276
|
+
hess_blocks = h_proj_vals[:, None, None] * proj_ops + h_perp_vals[:, None, None] * (identity - proj_ops)
|
|
277
|
+
|
|
278
|
+
# --- Accumulate into Cartesian Hessian ---
|
|
279
|
+
for k, (idx_i, idx_j) in enumerate(zip(i_idx, j_idx)):
|
|
280
|
+
block = hess_blocks[k]
|
|
281
|
+
self._add_block(idx_i, idx_i, block)
|
|
282
|
+
self._add_block(idx_j, idx_j, block)
|
|
283
|
+
self._add_block(idx_i, idx_j, -block)
|
|
284
|
+
self._add_block(idx_j, idx_i, -block)
|
|
356
285
|
|
|
357
|
-
#
|
|
358
|
-
|
|
286
|
+
# 5. Symmetrize & Clean
|
|
287
|
+
self.cart_hess = (self.cart_hess + self.cart_hess.T) / 2.0
|
|
359
288
|
|
|
289
|
+
# Project out TR/ROT modes
|
|
290
|
+
try:
|
|
291
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(
|
|
292
|
+
self.cart_hess, element_list, coord
|
|
293
|
+
)
|
|
294
|
+
if not np.all(np.isfinite(hess_proj)):
|
|
295
|
+
print("Warning: Projection produced NaNs. Using unprojected Hessian.")
|
|
296
|
+
hess_proj = self.cart_hess
|
|
297
|
+
except Exception as e:
|
|
298
|
+
print(f"Warning: Projection failed ({e}). Using unprojected Hessian.")
|
|
299
|
+
hess_proj = self.cart_hess
|
|
300
|
+
|
|
301
|
+
if not np.all(np.isfinite(hess_proj)):
|
|
302
|
+
print("CRITICAL: Hessian contains NaNs after generation. Resetting to Identity.")
|
|
303
|
+
hess_proj = np.eye(n_atoms*3)
|
|
304
|
+
|
|
360
305
|
return hess_proj
|