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,236 @@
|
|
|
1
|
+
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from multioptpy.Utils.bond_connectivity import BondConnectivity
|
|
5
|
+
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib
|
|
6
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
7
|
+
from multioptpy.ModelHessian.calc_params import stretch2, bend2, torsion2
|
|
8
|
+
|
|
9
|
+
class FischerApproxHessian:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
"""
|
|
12
|
+
Fischer's Model Hessian implementation
|
|
13
|
+
Ref: Fischer and Almlöf, J. Phys. Chem., 1992, 96, 24, 9768–9774
|
|
14
|
+
"""
|
|
15
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
16
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
17
|
+
self.bond_factor = 1.3 # Bond detection threshold factor
|
|
18
|
+
|
|
19
|
+
def calc_bond_force_const(self, r_ab, r_ab_cov):
|
|
20
|
+
"""Calculate force constant for bond stretching"""
|
|
21
|
+
return 0.3601 * np.exp(-1.944 * (r_ab - r_ab_cov))
|
|
22
|
+
|
|
23
|
+
def calc_bend_force_const(self, r_ab, r_ac, r_ab_cov, r_ac_cov):
|
|
24
|
+
"""Calculate force constant for angle bending"""
|
|
25
|
+
val = r_ab_cov * r_ac_cov
|
|
26
|
+
if abs(val) < 1.0e-10:
|
|
27
|
+
return 0.0 # Avoid division by zero
|
|
28
|
+
|
|
29
|
+
return 0.089 + 0.11 / (val) ** (-0.42) * np.exp(
|
|
30
|
+
-0.44 * (r_ab + r_ac - r_ab_cov - r_ac_cov)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
def calc_dihedral_force_const(self, r_ab, r_ab_cov, bond_sum):
|
|
34
|
+
"""Calculate force constant for dihedral torsion"""
|
|
35
|
+
val = r_ab * r_ab_cov
|
|
36
|
+
if abs(val) < 1.0e-10:
|
|
37
|
+
return 0.0 # Avoid division by zero
|
|
38
|
+
return 0.0015 + 14.0 * max(bond_sum, 0) ** 0.57 / (val) ** 4.0 * np.exp(
|
|
39
|
+
-2.85 * (r_ab - r_ab_cov)
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def get_bond_connectivity(self, coord, element_list):
|
|
43
|
+
"""
|
|
44
|
+
Calculate bond connectivity matrix and related data
|
|
45
|
+
Returns:
|
|
46
|
+
bond_mat: Bond connectivity matrix
|
|
47
|
+
dist_mat: Distance matrix between atoms
|
|
48
|
+
pair_cov_radii_mat: Matrix of covalent radii sums
|
|
49
|
+
"""
|
|
50
|
+
n_atoms = len(coord)
|
|
51
|
+
dist_mat = np.zeros((n_atoms, n_atoms))
|
|
52
|
+
pair_cov_radii_mat = np.zeros((n_atoms, n_atoms))
|
|
53
|
+
|
|
54
|
+
for i in range(n_atoms):
|
|
55
|
+
for j in range(i+1, n_atoms):
|
|
56
|
+
dist = np.linalg.norm(coord[i] - coord[j])
|
|
57
|
+
dist_mat[i, j] = dist_mat[j, i] = dist
|
|
58
|
+
|
|
59
|
+
cov_sum = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
60
|
+
pair_cov_radii_mat[i, j] = pair_cov_radii_mat[j, i] = cov_sum
|
|
61
|
+
|
|
62
|
+
# Bond connectivity matrix (True if bond exists between atoms)
|
|
63
|
+
bond_mat = dist_mat <= (pair_cov_radii_mat * self.bond_factor)
|
|
64
|
+
np.fill_diagonal(bond_mat, False) # No self-bonds
|
|
65
|
+
|
|
66
|
+
return bond_mat, dist_mat, pair_cov_radii_mat
|
|
67
|
+
|
|
68
|
+
def count_bonds_for_dihedral(self, bond_mat, central_atoms):
|
|
69
|
+
"""Count bonds connected to the central atoms of a dihedral"""
|
|
70
|
+
a, b = central_atoms
|
|
71
|
+
# Sum bonds for both central atoms and subtract 2 (the bond between them is counted twice)
|
|
72
|
+
bond_sum = bond_mat[a].sum() + bond_mat[b].sum() - 2
|
|
73
|
+
return bond_sum
|
|
74
|
+
|
|
75
|
+
def fischer_bond(self, coord, element_list):
|
|
76
|
+
"""Calculate Hessian components for bond stretching"""
|
|
77
|
+
BC = BondConnectivity()
|
|
78
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
79
|
+
bond_indices = BC.bond_connect_table(b_c_mat)
|
|
80
|
+
|
|
81
|
+
for idx in bond_indices:
|
|
82
|
+
i, j = idx
|
|
83
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
84
|
+
r_ij_cov = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
85
|
+
|
|
86
|
+
# Calculate force constant using Fischer's formula
|
|
87
|
+
force_const = self.calc_bond_force_const(r_ij, r_ij_cov)
|
|
88
|
+
|
|
89
|
+
# Convert to Cartesian coordinates
|
|
90
|
+
t_xyz = np.array([coord[i], coord[j]])
|
|
91
|
+
r, b_vec = stretch2(t_xyz)
|
|
92
|
+
|
|
93
|
+
for n in range(3):
|
|
94
|
+
for m in range(3):
|
|
95
|
+
self.cart_hess[3*i+n, 3*i+m] += force_const * b_vec[0][n] * b_vec[0][m]
|
|
96
|
+
self.cart_hess[3*j+n, 3*j+m] += force_const * b_vec[1][n] * b_vec[1][m]
|
|
97
|
+
self.cart_hess[3*i+n, 3*j+m] += force_const * b_vec[0][n] * b_vec[1][m]
|
|
98
|
+
self.cart_hess[3*j+n, 3*i+m] += force_const * b_vec[1][n] * b_vec[0][m]
|
|
99
|
+
|
|
100
|
+
def fischer_angle(self, coord, element_list):
|
|
101
|
+
"""Calculate Hessian components for angle bending"""
|
|
102
|
+
BC = BondConnectivity()
|
|
103
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
104
|
+
angle_indices = BC.angle_connect_table(b_c_mat)
|
|
105
|
+
|
|
106
|
+
for idx in angle_indices:
|
|
107
|
+
i, j, k = idx # i-j-k angle
|
|
108
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
109
|
+
r_jk = np.linalg.norm(coord[j] - coord[k])
|
|
110
|
+
r_ij_cov = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
111
|
+
r_jk_cov = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
112
|
+
|
|
113
|
+
# Calculate force constant using Fischer's formula
|
|
114
|
+
force_const = self.calc_bend_force_const(r_ij, r_jk, r_ij_cov, r_jk_cov)
|
|
115
|
+
|
|
116
|
+
# Convert to Cartesian coordinates
|
|
117
|
+
t_xyz = np.array([coord[i], coord[j], coord[k]])
|
|
118
|
+
theta, b_vec = bend2(t_xyz)
|
|
119
|
+
|
|
120
|
+
for n in range(3):
|
|
121
|
+
for m in range(3):
|
|
122
|
+
self.cart_hess[3*i+n, 3*i+m] += force_const * b_vec[0][n] * b_vec[0][m]
|
|
123
|
+
self.cart_hess[3*j+n, 3*j+m] += force_const * b_vec[1][n] * b_vec[1][m]
|
|
124
|
+
self.cart_hess[3*k+n, 3*k+m] += force_const * b_vec[2][n] * b_vec[2][m]
|
|
125
|
+
|
|
126
|
+
self.cart_hess[3*i+n, 3*j+m] += force_const * b_vec[0][n] * b_vec[1][m]
|
|
127
|
+
self.cart_hess[3*i+n, 3*k+m] += force_const * b_vec[0][n] * b_vec[2][m]
|
|
128
|
+
self.cart_hess[3*j+n, 3*i+m] += force_const * b_vec[1][n] * b_vec[0][m]
|
|
129
|
+
self.cart_hess[3*j+n, 3*k+m] += force_const * b_vec[1][n] * b_vec[2][m]
|
|
130
|
+
self.cart_hess[3*k+n, 3*i+m] += force_const * b_vec[2][n] * b_vec[0][m]
|
|
131
|
+
self.cart_hess[3*k+n, 3*j+m] += force_const * b_vec[2][n] * b_vec[1][m]
|
|
132
|
+
|
|
133
|
+
def fischer_dihedral(self, coord, element_list, bond_mat):
|
|
134
|
+
"""Calculate Hessian components for dihedral torsions with linearity check"""
|
|
135
|
+
BC = BondConnectivity()
|
|
136
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
137
|
+
dihedral_indices = BC.dihedral_angle_connect_table(b_c_mat)
|
|
138
|
+
|
|
139
|
+
# Helper to calculate sin^2 of bond angle
|
|
140
|
+
def get_sin_sq_angle(idx1, idx2, idx3):
|
|
141
|
+
v1 = coord[idx1] - coord[idx2]
|
|
142
|
+
v2 = coord[idx3] - coord[idx2]
|
|
143
|
+
# Cross product magnitude squared
|
|
144
|
+
cross_prod = np.cross(v1, v2)
|
|
145
|
+
cross_sq = np.dot(cross_prod, cross_prod)
|
|
146
|
+
# Squared norms
|
|
147
|
+
n1_sq = np.dot(v1, v1)
|
|
148
|
+
n2_sq = np.dot(v2, v2)
|
|
149
|
+
if n1_sq * n2_sq < 1e-12:
|
|
150
|
+
return 0.0
|
|
151
|
+
return cross_sq / (n1_sq * n2_sq)
|
|
152
|
+
|
|
153
|
+
for idx in dihedral_indices:
|
|
154
|
+
# i-j-k-l dihedral
|
|
155
|
+
i, j, k, l = idx
|
|
156
|
+
|
|
157
|
+
# --- Linearity Check ---
|
|
158
|
+
# Check bond angles I-J-K and J-K-L.
|
|
159
|
+
# If atoms are linear, the torsion definition is singular (B-matrix blows up).
|
|
160
|
+
# We use sin^2(theta) because it avoids acos and is efficient.
|
|
161
|
+
sin_sq_ijk = get_sin_sq_angle(i, j, k)
|
|
162
|
+
sin_sq_jkl = get_sin_sq_angle(j, k, l)
|
|
163
|
+
|
|
164
|
+
# Threshold: if sin(theta) < 0.17 (approx 10 degrees from linear), dampen or skip.
|
|
165
|
+
# Using sin^2 < 0.03 as cutoff.
|
|
166
|
+
# The Wilson B-matrix scales as 1/sin(theta).
|
|
167
|
+
# If we are too close to linear, we must skip to avoid numerical explosion.
|
|
168
|
+
if sin_sq_ijk < 1.0e-3 or sin_sq_jkl < 1.0e-3:
|
|
169
|
+
continue
|
|
170
|
+
# -----------------------
|
|
171
|
+
|
|
172
|
+
# Central bond in dihedral (j-k)
|
|
173
|
+
r_jk = np.linalg.norm(coord[j] - coord[k])
|
|
174
|
+
r_jk_cov = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
175
|
+
|
|
176
|
+
# Count bonds to central atoms
|
|
177
|
+
bond_sum = self.count_bonds_for_dihedral(bond_mat, (j, k))
|
|
178
|
+
|
|
179
|
+
# Calculate force constant
|
|
180
|
+
force_const = self.calc_dihedral_force_const(r_jk, r_jk_cov, bond_sum)
|
|
181
|
+
|
|
182
|
+
# Optional: Additional damping can be applied here if desired,
|
|
183
|
+
# but the skip above is the most robust fix for the singularity.
|
|
184
|
+
|
|
185
|
+
# Convert to Cartesian coordinates
|
|
186
|
+
t_xyz = np.array([coord[i], coord[j], coord[k], coord[l]])
|
|
187
|
+
|
|
188
|
+
# Note: torsion2 might still be unstable if not skipped
|
|
189
|
+
try:
|
|
190
|
+
tau, b_vec = torsion2(t_xyz)
|
|
191
|
+
except Exception:
|
|
192
|
+
# Fallback if torsion2 fails numerically
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
# Iterate over all pairs of atoms in the dihedral
|
|
196
|
+
atom_indices = [i, j, k, l]
|
|
197
|
+
|
|
198
|
+
for a in range(4):
|
|
199
|
+
for b in range(4):
|
|
200
|
+
atom_a = atom_indices[a]
|
|
201
|
+
atom_b = atom_indices[b]
|
|
202
|
+
|
|
203
|
+
vec_a = b_vec[a]
|
|
204
|
+
vec_b = b_vec[b]
|
|
205
|
+
|
|
206
|
+
# Update the 3x3 Hessian block
|
|
207
|
+
for n in range(3):
|
|
208
|
+
for m in range(3):
|
|
209
|
+
val = force_const * vec_a[n] * vec_b[m]
|
|
210
|
+
self.cart_hess[3*atom_a+n, 3*atom_b+m] += val
|
|
211
|
+
|
|
212
|
+
def main(self, coord, element_list, cart_gradient):
|
|
213
|
+
"""Main function to generate Fischer's approximate Hessian"""
|
|
214
|
+
print("Generating Fischer's approximate hessian...")
|
|
215
|
+
|
|
216
|
+
# Initialize Hessian matrix
|
|
217
|
+
n_atoms = len(coord)
|
|
218
|
+
self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
|
|
219
|
+
|
|
220
|
+
# Get bond connectivity and distance information
|
|
221
|
+
bond_mat, dist_mat, pair_cov_radii_mat = self.get_bond_connectivity(coord, element_list)
|
|
222
|
+
|
|
223
|
+
# Calculate Hessian components
|
|
224
|
+
self.fischer_bond(coord, element_list)
|
|
225
|
+
self.fischer_angle(coord, element_list)
|
|
226
|
+
self.fischer_dihedral(coord, element_list, bond_mat)
|
|
227
|
+
|
|
228
|
+
# Symmetrize the Hessian
|
|
229
|
+
for i in range(n_atoms*3):
|
|
230
|
+
for j in range(i):
|
|
231
|
+
self.cart_hess[i, j] = self.cart_hess[j, i]
|
|
232
|
+
|
|
233
|
+
# Project out translational and rotational modes
|
|
234
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
|
|
235
|
+
|
|
236
|
+
return hess_proj
|
|
@@ -0,0 +1,360 @@
|
|
|
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 FischerD3ApproxHessian:
|
|
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
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
194
|
+
r_jk = np.linalg.norm(coord[j] - coord[k])
|
|
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])
|
|
197
|
+
|
|
198
|
+
force_const = self.calc_bend_force_const(r_ij, r_jk, r_ij_cov, r_jk_cov)
|
|
199
|
+
|
|
200
|
+
t_xyz = np.array([coord[i], coord[j], coord[k]])
|
|
201
|
+
theta, b_vec = bend2(t_xyz)
|
|
202
|
+
|
|
203
|
+
# Optimized: Use NumPy slicing and outer product
|
|
204
|
+
atoms = [i, j, k]
|
|
205
|
+
|
|
206
|
+
for m_idx, m_atom in enumerate(atoms):
|
|
207
|
+
for n_idx, n_atom in enumerate(atoms):
|
|
208
|
+
start_m, end_m = 3 * m_atom, 3 * m_atom + 3
|
|
209
|
+
start_n, end_n = 3 * n_atom, 3 * n_atom + 3
|
|
210
|
+
|
|
211
|
+
H_mn_block = force_const * np.outer(b_vec[m_idx], b_vec[n_idx])
|
|
212
|
+
self.cart_hess[start_m:end_m, start_n:end_n] += H_mn_block
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def fischer_dihedral(self, coord, element_list, bond_mat):
|
|
216
|
+
"""Calculate Hessian components for dihedral torsion (Optimized with singularity damping)"""
|
|
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
|
|
227
|
+
|
|
228
|
+
for idx in dihedral_indices:
|
|
229
|
+
i, j, k, l = idx
|
|
230
|
+
|
|
231
|
+
# Vector calculations
|
|
232
|
+
vec_ji = coord[i] - coord[j]
|
|
233
|
+
vec_jk = coord[k] - coord[j]
|
|
234
|
+
vec_kl = coord[l] - coord[k]
|
|
235
|
+
|
|
236
|
+
r_jk = np.linalg.norm(vec_jk)
|
|
237
|
+
r_jk_cov = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
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)
|
|
263
|
+
|
|
264
|
+
# --- Damping Factor Calculation ---
|
|
265
|
+
# The Wilson B-matrix contains 1/sin(theta) terms which diverge at 180 deg.
|
|
266
|
+
# We scale the force constant by sin^2(theta) to cancel this divergence.
|
|
267
|
+
# sin^2(theta) = 1 - cos^2(theta)
|
|
268
|
+
|
|
269
|
+
sin2_theta1 = 1.0 - min(cos_theta1**2, 1.0)
|
|
270
|
+
sin2_theta2 = 1.0 - min(cos_theta2**2, 1.0)
|
|
271
|
+
|
|
272
|
+
# Hard cutoff: If geometry is extremely linear, skip to avoid NaN
|
|
273
|
+
if sin2_theta1 < 1e-4 or sin2_theta2 < 1e-4:
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
# Apply scaling factor
|
|
277
|
+
# This ensures the force constant goes to 0 as the angle becomes linear
|
|
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]])
|
|
284
|
+
|
|
285
|
+
try:
|
|
286
|
+
tau, b_vec = torsion2(t_xyz)
|
|
287
|
+
except (ValueError, ArithmeticError):
|
|
288
|
+
# Skip if numerical errors occur in torsion calculation
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
# Optimized: Use NumPy slicing and outer product
|
|
292
|
+
atoms = [i, j, k, l]
|
|
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
|
|
301
|
+
|
|
302
|
+
# --- Optimized: Block assignment using slicing (and fixed logic) ---
|
|
303
|
+
def d3_dispersion_hessian(self, coord, element_list, bond_mat):
|
|
304
|
+
"""Calculate Hessian correction based on D3 dispersion forces (Optimized/Corrected)"""
|
|
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)
|
|
316
|
+
|
|
317
|
+
if r_ij < 0.1:
|
|
318
|
+
continue
|
|
319
|
+
|
|
320
|
+
# Calculate D3 Hessian contribution (3x3 block)
|
|
321
|
+
hess_block = self.d3_hessian_contribution(r_vec, r_ij, element_list[i], element_list[j])
|
|
322
|
+
|
|
323
|
+
# Use slicing for efficient block assignment
|
|
324
|
+
# H_ii += H_block, H_jj += H_block, H_ij -= H_block, H_ji -= H_block
|
|
325
|
+
start_i, end_i = 3 * i, 3 * i + 3
|
|
326
|
+
start_j, end_j = 3 * j, 3 * j + 3
|
|
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 ---
|
|
334
|
+
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...")
|
|
339
|
+
|
|
340
|
+
n_atoms = len(coord)
|
|
341
|
+
self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
|
|
342
|
+
|
|
343
|
+
# Calculate bond connectivity matrix ONCE (Optimized internally)
|
|
344
|
+
bond_mat, dist_mat, pair_cov_radii_mat = self.get_bond_connectivity(coord, element_list)
|
|
345
|
+
|
|
346
|
+
# Calculate Hessian components from Fischer model (Optimized internally with slicing)
|
|
347
|
+
self.fischer_bond(coord, element_list)
|
|
348
|
+
self.fischer_angle(coord, element_list)
|
|
349
|
+
self.fischer_dihedral(coord, element_list, bond_mat)
|
|
350
|
+
|
|
351
|
+
# Calculate Hessian components from D3 dispersion correction (Optimized internally with slicing)
|
|
352
|
+
self.d3_dispersion_hessian(coord, element_list, bond_mat)
|
|
353
|
+
|
|
354
|
+
# Optimized: Symmetrize the Hessian matrix
|
|
355
|
+
self.cart_hess = (self.cart_hess + self.cart_hess.T) / 2.0
|
|
356
|
+
|
|
357
|
+
# Project out rotational and translational modes
|
|
358
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
|
|
359
|
+
|
|
360
|
+
return hess_proj
|