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,918 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib, UFF_VDW_distance_lib, D4Parameters, triple_covalent_radii_lib
|
|
4
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
5
|
+
from multioptpy.ModelHessian.calc_params import torsion2, outofplane2
|
|
6
|
+
from multioptpy.Parameters.parameter import UFF_VDW_distance_lib
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SwartD4ApproxHessian:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
#Swart's Model Hessian augmented with D4 dispersion
|
|
12
|
+
#ref.: M. Swart, F. M. Bickelhaupt, Int. J. Quantum Chem., 2006, 106, 2536–2544.
|
|
13
|
+
#ref.: E. Caldeweyher, C. Bannwarth, S. Grimme, J. Chem. Phys., 2017, 147, 034112
|
|
14
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
15
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
16
|
+
self.kd = 2.00 # Dispersion scaling factor
|
|
17
|
+
|
|
18
|
+
self.kr = 0.35 # Bond stretching force constant scaling
|
|
19
|
+
self.kf = 0.15 # Angle bending force constant scaling
|
|
20
|
+
self.kt = 0.005 # Torsional force constant scaling
|
|
21
|
+
|
|
22
|
+
self.cutoff = 70.0 # Cutoff for long-range interactions
|
|
23
|
+
self.eps = 1.0e-12 # Small number for numerical stability
|
|
24
|
+
|
|
25
|
+
# D4 parameters
|
|
26
|
+
self.d4_params = D4Parameters()
|
|
27
|
+
|
|
28
|
+
# Cyano group parameters
|
|
29
|
+
self.cn_kr = 0.70 # Enhanced force constant for C≡N triple bond
|
|
30
|
+
self.cn_kf = 0.20 # Enhanced force constant for angles involving C≡N
|
|
31
|
+
self.cn_kt = 0.002 # Reduced force constant for torsions involving C≡N
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
def detect_cyano_groups(self, coord, element_list):
|
|
35
|
+
"""Detect C≡N triple bonds in the structure"""
|
|
36
|
+
cyano_atoms = [] # List of (C_idx, N_idx) tuples
|
|
37
|
+
|
|
38
|
+
for i in range(len(coord)):
|
|
39
|
+
if element_list[i] != 'C':
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
for j in range(len(coord)):
|
|
43
|
+
if i == j or element_list[j] != 'N':
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
# Calculate distance between C and N
|
|
47
|
+
x_ij = coord[i][0] - coord[j][0]
|
|
48
|
+
y_ij = coord[i][1] - coord[j][1]
|
|
49
|
+
z_ij = coord[i][2] - coord[j][2]
|
|
50
|
+
r_ij = np.sqrt(x_ij**2 + y_ij**2 + z_ij**2)
|
|
51
|
+
|
|
52
|
+
# Check if distance is close to a triple bond length
|
|
53
|
+
cn_triple_bond = triple_covalent_radii_lib('C') + triple_covalent_radii_lib('N')
|
|
54
|
+
|
|
55
|
+
if abs(r_ij - cn_triple_bond) < 0.3: # Within 0.3 bohr of ideal length
|
|
56
|
+
# Check if C is connected to only one other atom (besides N)
|
|
57
|
+
connections_to_c = 0
|
|
58
|
+
for k in range(len(coord)):
|
|
59
|
+
if k == i or k == j:
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
x_ik = coord[i][0] - coord[k][0]
|
|
63
|
+
y_ik = coord[i][1] - coord[k][1]
|
|
64
|
+
z_ik = coord[i][2] - coord[k][2]
|
|
65
|
+
r_ik = np.sqrt(x_ik**2 + y_ik**2 + z_ik**2)
|
|
66
|
+
|
|
67
|
+
cov_dist = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
68
|
+
if r_ik < 1.3 * cov_dist: # Using 1.3 as a factor to account for bond length variations
|
|
69
|
+
connections_to_c += 1
|
|
70
|
+
|
|
71
|
+
# If C has only one other connection, it's likely a terminal cyano group
|
|
72
|
+
if connections_to_c <= 1:
|
|
73
|
+
cyano_atoms.append((i, j))
|
|
74
|
+
|
|
75
|
+
return cyano_atoms
|
|
76
|
+
|
|
77
|
+
def calculate_coordination_numbers(self, coord, element_list):
|
|
78
|
+
"""Calculate atomic coordination numbers for D4 scaling"""
|
|
79
|
+
n_atoms = len(coord)
|
|
80
|
+
cn = np.zeros(n_atoms)
|
|
81
|
+
|
|
82
|
+
for i in range(n_atoms):
|
|
83
|
+
for j in range(n_atoms):
|
|
84
|
+
if i == j:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
# Calculate distance
|
|
88
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
89
|
+
|
|
90
|
+
# Get covalent radii
|
|
91
|
+
r_cov_i = covalent_radii_lib(element_list[i])
|
|
92
|
+
r_cov_j = covalent_radii_lib(element_list[j])
|
|
93
|
+
|
|
94
|
+
# Coordination number contribution using counting function
|
|
95
|
+
# k1 = 16.0, k2 = 4.0/3.0 (standard values from DFT-D4)
|
|
96
|
+
k1 = 16.0
|
|
97
|
+
k2 = 4.0/3.0
|
|
98
|
+
r0 = r_cov_i + r_cov_j
|
|
99
|
+
|
|
100
|
+
# Avoid overflow in exp
|
|
101
|
+
if k1 * (r_ij / r0 - 1.0) > 25.0:
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
cn_contrib = 1.0 / (1.0 + np.exp(-k1 * (k2 * r0 / r_ij - 1.0)))
|
|
105
|
+
cn[i] += cn_contrib
|
|
106
|
+
|
|
107
|
+
return cn
|
|
108
|
+
|
|
109
|
+
def estimate_atomic_charges(self, coord, element_list):
|
|
110
|
+
"""
|
|
111
|
+
Estimate atomic charges using electronegativity equalization
|
|
112
|
+
Simplified version for Hessian generation
|
|
113
|
+
"""
|
|
114
|
+
n_atoms = len(coord)
|
|
115
|
+
charges = np.zeros(n_atoms)
|
|
116
|
+
|
|
117
|
+
# Calculate reference electronegativities
|
|
118
|
+
en_list = [self.d4_params.get_electronegativity(elem) for elem in element_list]
|
|
119
|
+
|
|
120
|
+
# Simple charge estimation based on electronegativity differences
|
|
121
|
+
# This is a very simplified model
|
|
122
|
+
for i in range(n_atoms):
|
|
123
|
+
for j in range(i+1, n_atoms):
|
|
124
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
125
|
+
r_cov_sum = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
126
|
+
|
|
127
|
+
# Only consider bonded atoms
|
|
128
|
+
if r_ij < 1.3 * r_cov_sum:
|
|
129
|
+
en_diff = en_list[j] - en_list[i]
|
|
130
|
+
charge_transfer = 0.1 * en_diff # Simple approximation
|
|
131
|
+
charges[i] += charge_transfer
|
|
132
|
+
charges[j] -= charge_transfer
|
|
133
|
+
|
|
134
|
+
return charges
|
|
135
|
+
|
|
136
|
+
def calc_force_const(self, alpha, covalent_length, distance):
|
|
137
|
+
"""Calculate force constant with exponential damping"""
|
|
138
|
+
force_const = np.exp(-1 * alpha * (distance / covalent_length - 1.0))
|
|
139
|
+
return force_const
|
|
140
|
+
|
|
141
|
+
def calc_vdw_force_const(self, alpha, r0, distance):
|
|
142
|
+
"""Calculate van der Waals force constant with exponential damping"""
|
|
143
|
+
vdw_force_const = np.exp(-1 * alpha * (r0 - distance) ** 2)
|
|
144
|
+
return vdw_force_const
|
|
145
|
+
|
|
146
|
+
def d4_damping_function(self, r_ij, r0, order=6):
|
|
147
|
+
"""D4 rational damping function"""
|
|
148
|
+
a1 = self.d4_params.a1
|
|
149
|
+
a2 = self.d4_params.a2
|
|
150
|
+
|
|
151
|
+
if order == 6:
|
|
152
|
+
return 1.0 / (1.0 + 6.0 * (r_ij / (a1 * r0)) ** a2)
|
|
153
|
+
elif order == 8:
|
|
154
|
+
return 1.0 / (1.0 + 6.0 * (r_ij / (a2 * r0)) ** a1)
|
|
155
|
+
return 0.0
|
|
156
|
+
|
|
157
|
+
def charge_scale_factor(self, charge, element):
|
|
158
|
+
"""Calculate charge scaling factor for D4"""
|
|
159
|
+
ga = self.d4_params.ga # D4 charge scaling parameter
|
|
160
|
+
q_ref = self.d4_params.get_electronegativity(element)
|
|
161
|
+
|
|
162
|
+
# Prevent numerical issues with large exponents
|
|
163
|
+
exp_arg = -ga * abs(charge)
|
|
164
|
+
if exp_arg < -50.0: # Avoid underflow
|
|
165
|
+
return 0.0
|
|
166
|
+
|
|
167
|
+
return np.exp(exp_arg)
|
|
168
|
+
|
|
169
|
+
def get_d4_parameters(self, elem1, elem2, q1=0.0, q2=0.0, cn1=None, cn2=None):
|
|
170
|
+
"""Get D4 parameters for a pair of elements with charge scaling"""
|
|
171
|
+
# Get polarizabilities
|
|
172
|
+
alpha1 = self.d4_params.get_polarizability(elem1)
|
|
173
|
+
alpha2 = self.d4_params.get_polarizability(elem2)
|
|
174
|
+
|
|
175
|
+
# Charge scaling
|
|
176
|
+
qscale1 = self.charge_scale_factor(q1, elem1)
|
|
177
|
+
qscale2 = self.charge_scale_factor(q2, elem2)
|
|
178
|
+
|
|
179
|
+
# Get R4/R2 values
|
|
180
|
+
r4r2_1 = self.d4_params.get_r4r2(elem1)
|
|
181
|
+
r4r2_2 = self.d4_params.get_r4r2(elem2)
|
|
182
|
+
|
|
183
|
+
# C6 coefficients with charge scaling
|
|
184
|
+
c6_1 = alpha1 * r4r2_1 * qscale1
|
|
185
|
+
c6_2 = alpha2 * r4r2_2 * qscale2
|
|
186
|
+
c6_param = 2.0 * c6_1 * c6_2 / (c6_1 + c6_2) # Effective C6 using harmonic mean
|
|
187
|
+
|
|
188
|
+
# C8 coefficients
|
|
189
|
+
c8_param = 3.0 * c6_param * np.sqrt(r4r2_1 * r4r2_2)
|
|
190
|
+
|
|
191
|
+
# r0 parameter (combined vdW radii)
|
|
192
|
+
r0_param = np.sqrt(UFF_VDW_distance_lib(elem1) * UFF_VDW_distance_lib(elem2))
|
|
193
|
+
|
|
194
|
+
return c6_param, c8_param, r0_param
|
|
195
|
+
|
|
196
|
+
def calc_d4_force_const(self, r_ij, c6_param, c8_param, r0_param):
|
|
197
|
+
"""Calculate D4 dispersion force constant"""
|
|
198
|
+
s6 = self.d4_params.s6
|
|
199
|
+
s8 = self.d4_params.s8
|
|
200
|
+
|
|
201
|
+
# Apply damping functions
|
|
202
|
+
damp6 = self.d4_damping_function(r_ij, r0_param, order=6)
|
|
203
|
+
damp8 = self.d4_damping_function(r_ij, r0_param, order=8)
|
|
204
|
+
|
|
205
|
+
# Energy terms (negative because dispersion is attractive)
|
|
206
|
+
e6 = -s6 * c6_param / r_ij ** 6 * damp6
|
|
207
|
+
e8 = -s8 * c8_param / r_ij ** 8 * damp8
|
|
208
|
+
|
|
209
|
+
# Force constant is the second derivative of energy
|
|
210
|
+
fc6 = s6 * c6_param * (42.0 / r_ij ** 8) * damp6
|
|
211
|
+
fc8 = s8 * c8_param * (72.0 / r_ij ** 10) * damp8
|
|
212
|
+
|
|
213
|
+
return fc6 + fc8
|
|
214
|
+
|
|
215
|
+
def swart_bond(self, coord, element_list, charges, cn):
|
|
216
|
+
"""Calculate bond stretching contributions to the Hessian with D4 dispersion"""
|
|
217
|
+
# Detect cyano groups
|
|
218
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
219
|
+
cyano_set = set()
|
|
220
|
+
for c_idx, n_idx in cyano_atoms:
|
|
221
|
+
cyano_set.add(c_idx)
|
|
222
|
+
cyano_set.add(n_idx)
|
|
223
|
+
|
|
224
|
+
for i in range(len(coord)):
|
|
225
|
+
for j in range(i):
|
|
226
|
+
|
|
227
|
+
x_ij = coord[i][0] - coord[j][0]
|
|
228
|
+
y_ij = coord[i][1] - coord[j][1]
|
|
229
|
+
z_ij = coord[i][2] - coord[j][2]
|
|
230
|
+
r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
|
|
231
|
+
r_ij = np.sqrt(r_ij_2)
|
|
232
|
+
covalent_length = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
233
|
+
|
|
234
|
+
# Get D4 parameters with charge scaling
|
|
235
|
+
c6_param, c8_param, r0_param = self.get_d4_parameters(
|
|
236
|
+
element_list[i], element_list[j],
|
|
237
|
+
q1=charges[i], q2=charges[j],
|
|
238
|
+
cn1=cn[i], cn2=cn[j]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
# Calculate D4 dispersion contribution
|
|
242
|
+
d4_force_const = self.calc_d4_force_const(r_ij, c6_param, c8_param, r0_param)
|
|
243
|
+
|
|
244
|
+
# Check if this is a cyano bond
|
|
245
|
+
is_cyano_bond = False
|
|
246
|
+
for c_idx, n_idx in cyano_atoms:
|
|
247
|
+
if (i == c_idx and j == n_idx) or (i == n_idx and j == c_idx):
|
|
248
|
+
is_cyano_bond = True
|
|
249
|
+
break
|
|
250
|
+
|
|
251
|
+
# Apply appropriate force constant
|
|
252
|
+
if is_cyano_bond:
|
|
253
|
+
# Special force constant for C≡N triple bond
|
|
254
|
+
g_mm = self.cn_kr * self.calc_force_const(1.0, covalent_length, r_ij) + self.kd * d4_force_const
|
|
255
|
+
else:
|
|
256
|
+
# Regular Swart force constant with D4 dispersion
|
|
257
|
+
g_mm = self.kr * self.calc_force_const(1.0, covalent_length, r_ij) + self.kd * d4_force_const
|
|
258
|
+
|
|
259
|
+
# Calculate Hessian components
|
|
260
|
+
hess_xx = g_mm * x_ij ** 2 / r_ij_2
|
|
261
|
+
hess_xy = g_mm * x_ij * y_ij / r_ij_2
|
|
262
|
+
hess_xz = g_mm * x_ij * z_ij / r_ij_2
|
|
263
|
+
hess_yy = g_mm * y_ij ** 2 / r_ij_2
|
|
264
|
+
hess_yz = g_mm * y_ij * z_ij / r_ij_2
|
|
265
|
+
hess_zz = g_mm * z_ij ** 2 / r_ij_2
|
|
266
|
+
|
|
267
|
+
# Fill the Hessian matrix
|
|
268
|
+
self.cart_hess[i * 3][i * 3] += hess_xx
|
|
269
|
+
self.cart_hess[i * 3 + 1][i * 3] += hess_xy
|
|
270
|
+
self.cart_hess[i * 3 + 1][i * 3 + 1] += hess_yy
|
|
271
|
+
self.cart_hess[i * 3 + 2][i * 3] += hess_xz
|
|
272
|
+
self.cart_hess[i * 3 + 2][i * 3 + 1] += hess_yz
|
|
273
|
+
self.cart_hess[i * 3 + 2][i * 3 + 2] += hess_zz
|
|
274
|
+
|
|
275
|
+
self.cart_hess[j * 3][j * 3] += hess_xx
|
|
276
|
+
self.cart_hess[j * 3 + 1][j * 3] += hess_xy
|
|
277
|
+
self.cart_hess[j * 3 + 1][j * 3 + 1] += hess_yy
|
|
278
|
+
self.cart_hess[j * 3 + 2][j * 3] += hess_xz
|
|
279
|
+
self.cart_hess[j * 3 + 2][j * 3 + 1] += hess_yz
|
|
280
|
+
self.cart_hess[j * 3 + 2][j * 3 + 2] += hess_zz
|
|
281
|
+
|
|
282
|
+
self.cart_hess[i * 3][j * 3] -= hess_xx
|
|
283
|
+
self.cart_hess[i * 3][j * 3 + 1] -= hess_xy
|
|
284
|
+
self.cart_hess[i * 3][j * 3 + 2] -= hess_xz
|
|
285
|
+
self.cart_hess[i * 3 + 1][j * 3] -= hess_xy
|
|
286
|
+
self.cart_hess[i * 3 + 1][j * 3 + 1] -= hess_yy
|
|
287
|
+
self.cart_hess[i * 3 + 1][j * 3 + 2] -= hess_yz
|
|
288
|
+
self.cart_hess[i * 3 + 2][j * 3] -= hess_xz
|
|
289
|
+
self.cart_hess[i * 3 + 2][j * 3 + 1] -= hess_yz
|
|
290
|
+
self.cart_hess[i * 3 + 2][j * 3 + 2] -= hess_zz
|
|
291
|
+
|
|
292
|
+
self.cart_hess[j * 3][i * 3] -= hess_xx
|
|
293
|
+
self.cart_hess[j * 3][i * 3 + 1] -= hess_xy
|
|
294
|
+
self.cart_hess[j * 3][i * 3 + 2] -= hess_xz
|
|
295
|
+
self.cart_hess[j * 3 + 1][i * 3] -= hess_xy
|
|
296
|
+
self.cart_hess[j * 3 + 1][i * 3 + 1] -= hess_yy
|
|
297
|
+
self.cart_hess[j * 3 + 1][i * 3 + 2] -= hess_yz
|
|
298
|
+
self.cart_hess[j * 3 + 2][i * 3] -= hess_xz
|
|
299
|
+
self.cart_hess[j * 3 + 2][i * 3 + 1] -= hess_yz
|
|
300
|
+
self.cart_hess[j * 3 + 2][i * 3 + 2] -= hess_zz
|
|
301
|
+
|
|
302
|
+
return
|
|
303
|
+
|
|
304
|
+
def swart_angle(self, coord, element_list, charges, cn):
|
|
305
|
+
"""Calculate angle bending contributions to the Hessian with D4 dispersion"""
|
|
306
|
+
# Detect cyano groups
|
|
307
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
308
|
+
cyano_set = set()
|
|
309
|
+
for c_idx, n_idx in cyano_atoms:
|
|
310
|
+
cyano_set.add(c_idx)
|
|
311
|
+
cyano_set.add(n_idx)
|
|
312
|
+
|
|
313
|
+
for i in range(len(coord)):
|
|
314
|
+
for j in range(len(coord)):
|
|
315
|
+
if i == j:
|
|
316
|
+
continue
|
|
317
|
+
x_ij = coord[i][0] - coord[j][0]
|
|
318
|
+
y_ij = coord[i][1] - coord[j][1]
|
|
319
|
+
z_ij = coord[i][2] - coord[j][2]
|
|
320
|
+
r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
|
|
321
|
+
r_ij = np.sqrt(r_ij_2)
|
|
322
|
+
covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
323
|
+
|
|
324
|
+
# Get D4 parameters with charge scaling for i-j pair
|
|
325
|
+
c6_ij, c8_ij, r0_ij = self.get_d4_parameters(
|
|
326
|
+
element_list[i], element_list[j],
|
|
327
|
+
q1=charges[i], q2=charges[j],
|
|
328
|
+
cn1=cn[i], cn2=cn[j]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
for k in range(j):
|
|
332
|
+
if i == k:
|
|
333
|
+
continue
|
|
334
|
+
x_ik = coord[i][0] - coord[k][0]
|
|
335
|
+
y_ik = coord[i][1] - coord[k][1]
|
|
336
|
+
z_ik = coord[i][2] - coord[k][2]
|
|
337
|
+
r_ik_2 = x_ik**2 + y_ik**2 + z_ik**2
|
|
338
|
+
r_ik = np.sqrt(r_ik_2)
|
|
339
|
+
covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
340
|
+
|
|
341
|
+
# Check for linear arrangement (cos_theta ~ 1.0)
|
|
342
|
+
error_check = x_ij * x_ik + y_ij * y_ik + z_ij * z_ik
|
|
343
|
+
error_check = error_check / (r_ij * r_ik)
|
|
344
|
+
|
|
345
|
+
if abs(error_check - 1.0) < self.eps:
|
|
346
|
+
continue
|
|
347
|
+
|
|
348
|
+
x_jk = coord[j][0] - coord[k][0]
|
|
349
|
+
y_jk = coord[j][1] - coord[k][1]
|
|
350
|
+
z_jk = coord[j][2] - coord[k][2]
|
|
351
|
+
r_jk_2 = x_jk**2 + y_jk**2 + z_jk**2
|
|
352
|
+
r_jk = np.sqrt(r_jk_2)
|
|
353
|
+
|
|
354
|
+
# Get D4 parameters with charge scaling for i-k pair
|
|
355
|
+
c6_ik, c8_ik, r0_ik = self.get_d4_parameters(
|
|
356
|
+
element_list[i], element_list[k],
|
|
357
|
+
q1=charges[i], q2=charges[k],
|
|
358
|
+
cn1=cn[i], cn2=cn[k]
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
# Calculate D4 dispersion contributions
|
|
362
|
+
d4_ij = self.calc_d4_force_const(r_ij, c6_ij, c8_ij, r0_ij)
|
|
363
|
+
d4_ik = self.calc_d4_force_const(r_ik, c6_ik, c8_ik, r0_ik)
|
|
364
|
+
|
|
365
|
+
# Calculate bond force constants with D4 dispersion
|
|
366
|
+
g_ij = self.calc_force_const(1.0, covalent_length_ij, r_ij) + 0.5 * self.kd * d4_ij
|
|
367
|
+
g_ik = self.calc_force_const(1.0, covalent_length_ik, r_ik) + 0.5 * self.kd * d4_ik
|
|
368
|
+
|
|
369
|
+
# Check if angle involves cyano group
|
|
370
|
+
is_cyano_angle = (i in cyano_set or j in cyano_set or k in cyano_set)
|
|
371
|
+
|
|
372
|
+
# Apply appropriate force constant
|
|
373
|
+
if is_cyano_angle:
|
|
374
|
+
# Special force constant for angles involving cyano groups
|
|
375
|
+
g_jk = self.cn_kf * g_ij * g_ik
|
|
376
|
+
else:
|
|
377
|
+
# Regular Swart force constant
|
|
378
|
+
g_jk = self.kf * g_ij * g_ik
|
|
379
|
+
|
|
380
|
+
# Calculate cross product for sin(theta)
|
|
381
|
+
r_cross_2 = (y_ij * z_ik - z_ij * y_ik) ** 2 + (z_ij * x_ik - x_ij * z_ik) ** 2 + (x_ij * y_ik - y_ij * x_ik) ** 2
|
|
382
|
+
|
|
383
|
+
if r_cross_2 < 1.0e-12:
|
|
384
|
+
r_cross = 0.0
|
|
385
|
+
else:
|
|
386
|
+
r_cross = np.sqrt(r_cross_2)
|
|
387
|
+
|
|
388
|
+
if r_ik > self.eps and r_ij > self.eps and r_jk > self.eps:
|
|
389
|
+
cos_theta = (r_ij_2 + r_ik_2 - r_jk_2) / (2.0 * r_ij * r_ik)
|
|
390
|
+
sin_theta = r_cross / (r_ij * r_ik)
|
|
391
|
+
|
|
392
|
+
dot_product_r_ij_r_ik = x_ij * x_ik + y_ij * y_ik + z_ij * z_ik
|
|
393
|
+
|
|
394
|
+
if sin_theta > self.eps: # non-linear
|
|
395
|
+
# Calculate derivatives for non-linear case
|
|
396
|
+
s_xj = (x_ij / r_ij * cos_theta - x_ik / r_ik) / (r_ij * sin_theta)
|
|
397
|
+
s_yj = (y_ij / r_ij * cos_theta - y_ik / r_ik) / (r_ij * sin_theta)
|
|
398
|
+
s_zj = (z_ij / r_ij * cos_theta - z_ik / r_ik) / (r_ij * sin_theta)
|
|
399
|
+
|
|
400
|
+
s_xk = (x_ik / r_ik * cos_theta - x_ij / r_ij) / (r_ik * sin_theta)
|
|
401
|
+
s_yk = (y_ik / r_ik * cos_theta - y_ij / r_ij) / (r_ik * sin_theta)
|
|
402
|
+
s_zk = (z_ik / r_ik * cos_theta - z_ij / r_ij) / (r_ik * sin_theta)
|
|
403
|
+
|
|
404
|
+
s_xi = -1 * s_xj - s_xk
|
|
405
|
+
s_yi = -1 * s_yj - s_yk
|
|
406
|
+
s_zi = -1 * s_zj - s_zk
|
|
407
|
+
|
|
408
|
+
s_j = [s_xj, s_yj, s_zj]
|
|
409
|
+
s_k = [s_xk, s_yk, s_zk]
|
|
410
|
+
s_i = [s_xi, s_yi, s_zi]
|
|
411
|
+
|
|
412
|
+
# Update Hessian for non-linear case
|
|
413
|
+
for l in range(3):
|
|
414
|
+
for m in range(3):
|
|
415
|
+
#-------------------------------------
|
|
416
|
+
if i > j:
|
|
417
|
+
tmp_val = g_jk * s_i[l] * s_j[m]
|
|
418
|
+
self.cart_hess[i * 3 + l][j * 3 + m] += tmp_val
|
|
419
|
+
else:
|
|
420
|
+
tmp_val = g_jk * s_j[l] * s_i[m]
|
|
421
|
+
self.cart_hess[j * 3 + l][i * 3 + m] += tmp_val
|
|
422
|
+
|
|
423
|
+
#-------------------------------------
|
|
424
|
+
if i > k:
|
|
425
|
+
tmp_val = g_jk * s_i[l] * s_k[m]
|
|
426
|
+
self.cart_hess[i * 3 + l][k * 3 + m] += tmp_val
|
|
427
|
+
else:
|
|
428
|
+
tmp_val = g_jk * s_k[l] * s_i[m]
|
|
429
|
+
self.cart_hess[k * 3 + l][i * 3 + m] += tmp_val
|
|
430
|
+
|
|
431
|
+
#-------------------------------------
|
|
432
|
+
if j > k:
|
|
433
|
+
tmp_val = g_jk * s_j[l] * s_k[m]
|
|
434
|
+
self.cart_hess[j * 3 + l][k * 3 + m] += tmp_val
|
|
435
|
+
else:
|
|
436
|
+
tmp_val = g_jk * s_k[l] * s_j[m]
|
|
437
|
+
self.cart_hess[k * 3 + l][j * 3 + m] += tmp_val
|
|
438
|
+
#-------------------------------------
|
|
439
|
+
|
|
440
|
+
# Update diagonal blocks
|
|
441
|
+
for l in range(3):
|
|
442
|
+
for m in range(l):
|
|
443
|
+
tmp_val_1 = g_jk * s_j[l] * s_j[m]
|
|
444
|
+
tmp_val_2 = g_jk * s_i[l] * s_i[m]
|
|
445
|
+
tmp_val_3 = g_jk * s_k[l] * s_k[m]
|
|
446
|
+
|
|
447
|
+
self.cart_hess[j * 3 + l][j * 3 + m] += tmp_val_1
|
|
448
|
+
self.cart_hess[i * 3 + l][i * 3 + m] += tmp_val_2
|
|
449
|
+
self.cart_hess[k * 3 + l][k * 3 + m] += tmp_val_3
|
|
450
|
+
|
|
451
|
+
else: # linear
|
|
452
|
+
# Special handling for linear arrangements
|
|
453
|
+
if abs(y_ij) < self.eps and abs(z_ij) < self.eps:
|
|
454
|
+
x_1 = -1 * y_ij
|
|
455
|
+
y_1 = x_ij
|
|
456
|
+
z_1 = 0.0
|
|
457
|
+
x_2 = -1 * x_ij * z_ij
|
|
458
|
+
y_2 = -1 * y_ij * z_ij
|
|
459
|
+
z_2 = x_ij ** 2 + y_ij ** 2
|
|
460
|
+
else:
|
|
461
|
+
x_1 = 1.0
|
|
462
|
+
y_1 = 0.0
|
|
463
|
+
z_1 = 0.0
|
|
464
|
+
x_2 = 0.0
|
|
465
|
+
y_2 = 1.0
|
|
466
|
+
z_2 = 0.0
|
|
467
|
+
|
|
468
|
+
x = [x_1, x_2]
|
|
469
|
+
y = [y_1, y_2]
|
|
470
|
+
z = [z_1, z_2]
|
|
471
|
+
|
|
472
|
+
# Iterate over two perpendicular directions
|
|
473
|
+
for ii in range(2):
|
|
474
|
+
r_1 = np.sqrt(x[ii] ** 2 + y[ii] ** 2 + z[ii] ** 2)
|
|
475
|
+
cos_theta_x = x[ii] / r_1
|
|
476
|
+
cos_theta_y = y[ii] / r_1
|
|
477
|
+
cos_theta_z = z[ii] / r_1
|
|
478
|
+
|
|
479
|
+
s_xj = -1 * cos_theta_x / r_ij
|
|
480
|
+
s_yj = -1 * cos_theta_y / r_ij
|
|
481
|
+
s_zj = -1 * cos_theta_z / r_ij
|
|
482
|
+
s_xk = -1 * cos_theta_x / r_ik
|
|
483
|
+
s_yk = -1 * cos_theta_y / r_ik
|
|
484
|
+
s_zk = -1 * cos_theta_z / r_ik
|
|
485
|
+
|
|
486
|
+
s_xi = -1 * s_xj - s_xk
|
|
487
|
+
s_yi = -1 * s_yj - s_yk
|
|
488
|
+
s_zi = -1 * s_zj - s_zk
|
|
489
|
+
|
|
490
|
+
s_j = [s_xj, s_yj, s_zj]
|
|
491
|
+
s_k = [s_xk, s_yk, s_zk]
|
|
492
|
+
s_i = [s_xi, s_yi, s_zi]
|
|
493
|
+
|
|
494
|
+
# Update Hessian for linear case
|
|
495
|
+
for l in range(3):
|
|
496
|
+
for m in range(3):
|
|
497
|
+
#-------------------------------------
|
|
498
|
+
if i > j:
|
|
499
|
+
tmp_val = g_jk * s_i[l] * s_j[m]
|
|
500
|
+
self.cart_hess[i * 3 + l][j * 3 + m] += tmp_val
|
|
501
|
+
else:
|
|
502
|
+
tmp_val = g_jk * s_j[l] * s_i[m]
|
|
503
|
+
self.cart_hess[j * 3 + l][i * 3 + m] += tmp_val
|
|
504
|
+
#-------------------------------------
|
|
505
|
+
if i > k:
|
|
506
|
+
tmp_val = g_jk * s_i[l] * s_k[m]
|
|
507
|
+
self.cart_hess[i * 3 + l][k * 3 + m] += tmp_val
|
|
508
|
+
else:
|
|
509
|
+
tmp_val = g_jk * s_k[l] * s_i[m]
|
|
510
|
+
self.cart_hess[k * 3 + l][i * 3 + m] += tmp_val
|
|
511
|
+
#-------------------------------------
|
|
512
|
+
if j > k:
|
|
513
|
+
tmp_val = g_jk * s_j[l] * s_k[m]
|
|
514
|
+
self.cart_hess[j * 3 + l][k * 3 + m] += tmp_val
|
|
515
|
+
else:
|
|
516
|
+
tmp_val = g_jk * s_k[l] * s_j[m]
|
|
517
|
+
self.cart_hess[k * 3 + l][j * 3 + m] += tmp_val
|
|
518
|
+
#-------------------------------------
|
|
519
|
+
|
|
520
|
+
# Update diagonal blocks for linear case
|
|
521
|
+
for l in range(3):
|
|
522
|
+
for m in range(l):
|
|
523
|
+
tmp_val_1 = g_jk * s_j[l] * s_j[m]
|
|
524
|
+
tmp_val_2 = g_jk * s_i[l] * s_i[m]
|
|
525
|
+
tmp_val_3 = g_jk * s_k[l] * s_k[m]
|
|
526
|
+
|
|
527
|
+
self.cart_hess[j * 3 + l][j * 3 + m] += tmp_val_1
|
|
528
|
+
self.cart_hess[i * 3 + l][i * 3 + m] += tmp_val_2
|
|
529
|
+
self.cart_hess[k * 3 + l][k * 3 + m] += tmp_val_3
|
|
530
|
+
else:
|
|
531
|
+
pass # Skip if any distance is too small
|
|
532
|
+
|
|
533
|
+
# Make the Hessian symmetric for angle terms
|
|
534
|
+
n_basis = len(coord) * 3
|
|
535
|
+
for i in range(n_basis):
|
|
536
|
+
for j in range(i):
|
|
537
|
+
if abs(self.cart_hess[i][j] - self.cart_hess[j][i]) > 1.0e-10:
|
|
538
|
+
avg = (self.cart_hess[i][j] + self.cart_hess[j][i]) / 2.0
|
|
539
|
+
self.cart_hess[i][j] = avg
|
|
540
|
+
self.cart_hess[j][i] = avg
|
|
541
|
+
|
|
542
|
+
return
|
|
543
|
+
|
|
544
|
+
def swart_dihedral_angle(self, coord, element_list, charges, cn):
|
|
545
|
+
"""Calculate dihedral angle contributions to the Hessian with D4 dispersion"""
|
|
546
|
+
# Detect cyano groups
|
|
547
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
548
|
+
cyano_set = set()
|
|
549
|
+
for c_idx, n_idx in cyano_atoms:
|
|
550
|
+
cyano_set.add(c_idx)
|
|
551
|
+
cyano_set.add(n_idx)
|
|
552
|
+
|
|
553
|
+
for j in range(len(coord)):
|
|
554
|
+
t_xyz_2 = coord[j]
|
|
555
|
+
|
|
556
|
+
for k in range(len(coord)):
|
|
557
|
+
if j >= k:
|
|
558
|
+
continue
|
|
559
|
+
t_xyz_3 = coord[k]
|
|
560
|
+
for i in range(len(coord)):
|
|
561
|
+
ij = (len(coord) * j) + (i + 1)
|
|
562
|
+
if i >= j:
|
|
563
|
+
continue
|
|
564
|
+
if i >= k:
|
|
565
|
+
continue
|
|
566
|
+
|
|
567
|
+
t_xyz_1 = coord[i]
|
|
568
|
+
|
|
569
|
+
for l in range(len(coord)):
|
|
570
|
+
kl = (len(coord) * k) + (l + 1)
|
|
571
|
+
|
|
572
|
+
if ij <= kl:
|
|
573
|
+
continue
|
|
574
|
+
if l >= i:
|
|
575
|
+
continue
|
|
576
|
+
if l >= j:
|
|
577
|
+
continue
|
|
578
|
+
if l >= k:
|
|
579
|
+
continue
|
|
580
|
+
|
|
581
|
+
t_xyz_4 = coord[l]
|
|
582
|
+
r_ij = coord[i] - coord[j]
|
|
583
|
+
r_jk = coord[j] - coord[k]
|
|
584
|
+
r_kl = coord[k] - coord[l]
|
|
585
|
+
|
|
586
|
+
covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
587
|
+
covalent_length_jk = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
588
|
+
covalent_length_kl = covalent_radii_lib(element_list[k]) + covalent_radii_lib(element_list[l])
|
|
589
|
+
|
|
590
|
+
# Calculate vector magnitudes
|
|
591
|
+
r_ij_2 = np.sum(r_ij ** 2)
|
|
592
|
+
r_jk_2 = np.sum(r_jk ** 2)
|
|
593
|
+
r_kl_2 = np.sum(r_kl ** 2)
|
|
594
|
+
norm_r_ij = np.sqrt(r_ij_2)
|
|
595
|
+
norm_r_jk = np.sqrt(r_jk_2)
|
|
596
|
+
norm_r_kl = np.sqrt(r_kl_2)
|
|
597
|
+
|
|
598
|
+
# Skip if angle is too shallow (less than 35 degrees)
|
|
599
|
+
a35 = (35.0/180)* np.pi
|
|
600
|
+
cosfi_max = np.cos(a35)
|
|
601
|
+
cosfi2 = np.dot(r_ij, r_jk) / np.sqrt(r_ij_2 * r_jk_2)
|
|
602
|
+
if abs(cosfi2) > cosfi_max:
|
|
603
|
+
continue
|
|
604
|
+
cosfi3 = np.dot(r_kl, r_jk) / np.sqrt(r_kl_2 * r_jk_2)
|
|
605
|
+
if abs(cosfi3) > cosfi_max:
|
|
606
|
+
continue
|
|
607
|
+
|
|
608
|
+
# Get D4 parameters for each atom pair with charge scaling
|
|
609
|
+
c6_ij, c8_ij, r0_ij = self.get_d4_parameters(
|
|
610
|
+
element_list[i], element_list[j],
|
|
611
|
+
q1=charges[i], q2=charges[j],
|
|
612
|
+
cn1=cn[i], cn2=cn[j]
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
c6_jk, c8_jk, r0_jk = self.get_d4_parameters(
|
|
616
|
+
element_list[j], element_list[k],
|
|
617
|
+
q1=charges[j], q2=charges[k],
|
|
618
|
+
cn1=cn[j], cn2=cn[k]
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
c6_kl, c8_kl, r0_kl = self.get_d4_parameters(
|
|
622
|
+
element_list[k], element_list[l],
|
|
623
|
+
q1=charges[k], q2=charges[l],
|
|
624
|
+
cn1=cn[k], cn2=cn[l]
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# Calculate D4 dispersion contributions
|
|
628
|
+
d4_ij = self.calc_d4_force_const(norm_r_ij, c6_ij, c8_ij, r0_ij)
|
|
629
|
+
d4_jk = self.calc_d4_force_const(norm_r_jk, c6_jk, c8_jk, r0_jk)
|
|
630
|
+
d4_kl = self.calc_d4_force_const(norm_r_kl, c6_kl, c8_kl, r0_kl)
|
|
631
|
+
|
|
632
|
+
# Calculate bond force constants with D4 dispersion
|
|
633
|
+
g_ij = self.calc_force_const(1.0, covalent_length_ij, norm_r_ij) + 0.5 * self.kd * d4_ij
|
|
634
|
+
g_jk = self.calc_force_const(1.0, covalent_length_jk, norm_r_jk) + 0.5 * self.kd * d4_jk
|
|
635
|
+
g_kl = self.calc_force_const(1.0, covalent_length_kl, norm_r_kl) + 0.5 * self.kd * d4_kl
|
|
636
|
+
|
|
637
|
+
# Check if torsion involves cyano group
|
|
638
|
+
is_cyano_torsion = False
|
|
639
|
+
if i in cyano_set or j in cyano_set or k in cyano_set or l in cyano_set:
|
|
640
|
+
is_cyano_torsion = True
|
|
641
|
+
|
|
642
|
+
# Adjust force constant for cyano groups - they have flatter torsional potentials
|
|
643
|
+
if is_cyano_torsion:
|
|
644
|
+
t_ij = self.cn_kt * g_ij * g_jk * g_kl
|
|
645
|
+
else:
|
|
646
|
+
t_ij = self.kt * g_ij * g_jk * g_kl
|
|
647
|
+
|
|
648
|
+
# Calculate torsion angle and derivatives
|
|
649
|
+
t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
|
|
650
|
+
tau, c = torsion2(t_xyz)
|
|
651
|
+
s_i = c[0]
|
|
652
|
+
s_j = c[1]
|
|
653
|
+
s_k = c[2]
|
|
654
|
+
s_l = c[3]
|
|
655
|
+
|
|
656
|
+
# Update Hessian with torsional contributions
|
|
657
|
+
for n in range(3):
|
|
658
|
+
for m in range(3):
|
|
659
|
+
self.cart_hess[3 * i + n][3 * j + m] += t_ij * s_i[n] * s_j[m]
|
|
660
|
+
self.cart_hess[3 * i + n][3 * k + m] += t_ij * s_i[n] * s_k[m]
|
|
661
|
+
self.cart_hess[3 * i + n][3 * l + m] += t_ij * s_i[n] * s_l[m]
|
|
662
|
+
self.cart_hess[3 * j + n][3 * k + m] += t_ij * s_j[n] * s_k[m]
|
|
663
|
+
self.cart_hess[3 * j + n][3 * l + m] += t_ij * s_j[n] * s_l[m]
|
|
664
|
+
self.cart_hess[3 * k + n][3 * l + m] += t_ij * s_k[n] * s_l[m]
|
|
665
|
+
|
|
666
|
+
# Update diagonal blocks
|
|
667
|
+
for n in range(3):
|
|
668
|
+
for m in range(n):
|
|
669
|
+
self.cart_hess[3 * i + n][3 * i + m] += t_ij * s_i[n] * s_i[m]
|
|
670
|
+
self.cart_hess[3 * j + n][3 * j + m] += t_ij * s_j[n] * s_j[m]
|
|
671
|
+
self.cart_hess[3 * k + n][3 * k + m] += t_ij * s_k[n] * s_k[m]
|
|
672
|
+
self.cart_hess[3 * l + n][3 * l + m] += t_ij * s_l[n] * s_l[m]
|
|
673
|
+
|
|
674
|
+
return
|
|
675
|
+
|
|
676
|
+
def swart_out_of_plane(self, coord, element_list, charges, cn):
|
|
677
|
+
"""Calculate out-of-plane bending contributions to the Hessian with D4 dispersion"""
|
|
678
|
+
# Detect cyano groups
|
|
679
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
680
|
+
cyano_set = set()
|
|
681
|
+
for c_idx, n_idx in cyano_atoms:
|
|
682
|
+
cyano_set.add(c_idx)
|
|
683
|
+
cyano_set.add(n_idx)
|
|
684
|
+
|
|
685
|
+
for i in range(len(coord)):
|
|
686
|
+
t_xyz_4 = coord[i]
|
|
687
|
+
for j in range(len(coord)):
|
|
688
|
+
if i >= j:
|
|
689
|
+
continue
|
|
690
|
+
t_xyz_1 = coord[j]
|
|
691
|
+
for k in range(len(coord)):
|
|
692
|
+
ij = (len(coord) * j) + (i + 1)
|
|
693
|
+
if i >= k:
|
|
694
|
+
continue
|
|
695
|
+
if j >= k:
|
|
696
|
+
continue
|
|
697
|
+
t_xyz_2 = coord[k]
|
|
698
|
+
|
|
699
|
+
for l in range(len(coord)):
|
|
700
|
+
kl = (len(coord) * k) + (l + 1)
|
|
701
|
+
if i >= l:
|
|
702
|
+
continue
|
|
703
|
+
if j >= l:
|
|
704
|
+
continue
|
|
705
|
+
if k >= l:
|
|
706
|
+
continue
|
|
707
|
+
if ij <= kl:
|
|
708
|
+
continue
|
|
709
|
+
t_xyz_3 = coord[l]
|
|
710
|
+
|
|
711
|
+
r_ij = coord[i] - coord[j]
|
|
712
|
+
r_ik = coord[i] - coord[k]
|
|
713
|
+
r_il = coord[i] - coord[l]
|
|
714
|
+
|
|
715
|
+
covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
716
|
+
covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
717
|
+
covalent_length_il = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[l])
|
|
718
|
+
|
|
719
|
+
r_ij_2 = np.sum(r_ij ** 2)
|
|
720
|
+
r_ik_2 = np.sum(r_ik ** 2)
|
|
721
|
+
r_il_2 = np.sum(r_il ** 2)
|
|
722
|
+
norm_r_ij = np.sqrt(r_ij_2)
|
|
723
|
+
norm_r_ik = np.sqrt(r_ik_2)
|
|
724
|
+
norm_r_il = np.sqrt(r_il_2)
|
|
725
|
+
|
|
726
|
+
# Skip if atoms are nearly collinear
|
|
727
|
+
cosfi2 = np.dot(r_ij, r_ik) / np.sqrt(r_ij_2 * r_ik_2)
|
|
728
|
+
if abs(abs(cosfi2) - 1.0) < 1.0e-1:
|
|
729
|
+
continue
|
|
730
|
+
cosfi3 = np.dot(r_ij, r_il) / np.sqrt(r_ij_2 * r_il_2)
|
|
731
|
+
if abs(abs(cosfi3) - 1.0) < 1.0e-1:
|
|
732
|
+
continue
|
|
733
|
+
cosfi4 = np.dot(r_ik, r_il) / np.sqrt(r_ik_2 * r_il_2)
|
|
734
|
+
if abs(abs(cosfi4) - 1.0) < 1.0e-1:
|
|
735
|
+
continue
|
|
736
|
+
|
|
737
|
+
# Get D4 parameters with charge scaling for each atom pair
|
|
738
|
+
c6_ij, c8_ij, r0_ij = self.get_d4_parameters(
|
|
739
|
+
element_list[i], element_list[j],
|
|
740
|
+
q1=charges[i], q2=charges[j],
|
|
741
|
+
cn1=cn[i], cn2=cn[j]
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
c6_ik, c8_ik, r0_ik = self.get_d4_parameters(
|
|
745
|
+
element_list[i], element_list[k],
|
|
746
|
+
q1=charges[i], q2=charges[k],
|
|
747
|
+
cn1=cn[i], cn2=cn[k]
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
c6_il, c8_il, r0_il = self.get_d4_parameters(
|
|
751
|
+
element_list[i], element_list[l],
|
|
752
|
+
q1=charges[i], q2=charges[l],
|
|
753
|
+
cn1=cn[i], cn2=cn[l]
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
# Calculate D4 dispersion contributions
|
|
757
|
+
d4_ij = self.calc_d4_force_const(norm_r_ij, c6_ij, c8_ij, r0_ij)
|
|
758
|
+
d4_ik = self.calc_d4_force_const(norm_r_ik, c6_ik, c8_ik, r0_ik)
|
|
759
|
+
d4_il = self.calc_d4_force_const(norm_r_il, c6_il, c8_il, r0_il)
|
|
760
|
+
|
|
761
|
+
# Calculate bond force constants with D4 dispersion
|
|
762
|
+
g_ij = self.calc_force_const(1.0, covalent_length_ij, norm_r_ij) + 0.5 * self.kd * d4_ij
|
|
763
|
+
g_ik = self.calc_force_const(1.0, covalent_length_ik, norm_r_ik) + 0.5 * self.kd * d4_ik
|
|
764
|
+
g_il = self.calc_force_const(1.0, covalent_length_il, norm_r_il) + 0.5 * self.kd * d4_il
|
|
765
|
+
|
|
766
|
+
# Check if any atom is part of a cyano group
|
|
767
|
+
is_cyano_involved = (i in cyano_set or j in cyano_set or
|
|
768
|
+
k in cyano_set or l in cyano_set)
|
|
769
|
+
|
|
770
|
+
# Adjust force constant if cyano group is involved
|
|
771
|
+
if is_cyano_involved:
|
|
772
|
+
t_ij = 0.5 * self.kt * g_ij * g_ik * g_il # Reduce force constant for cyano
|
|
773
|
+
else:
|
|
774
|
+
t_ij = self.kt * g_ij * g_ik * g_il
|
|
775
|
+
|
|
776
|
+
# Calculate out-of-plane angle and derivatives
|
|
777
|
+
t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
|
|
778
|
+
theta, c = outofplane2(t_xyz)
|
|
779
|
+
|
|
780
|
+
s_i = c[0]
|
|
781
|
+
s_j = c[1]
|
|
782
|
+
s_k = c[2]
|
|
783
|
+
s_l = c[3]
|
|
784
|
+
|
|
785
|
+
# Update Hessian with out-of-plane contributions
|
|
786
|
+
for n in range(3):
|
|
787
|
+
for m in range(3):
|
|
788
|
+
self.cart_hess[i * 3 + n][j * 3 + m] += t_ij * s_i[n] * s_j[m]
|
|
789
|
+
self.cart_hess[i * 3 + n][k * 3 + m] += t_ij * s_i[n] * s_k[m]
|
|
790
|
+
self.cart_hess[i * 3 + n][l * 3 + m] += t_ij * s_i[n] * s_l[m]
|
|
791
|
+
self.cart_hess[j * 3 + n][k * 3 + m] += t_ij * s_j[n] * s_k[m]
|
|
792
|
+
self.cart_hess[j * 3 + n][l * 3 + m] += t_ij * s_j[n] * s_l[m]
|
|
793
|
+
self.cart_hess[k * 3 + n][l * 3 + m] += t_ij * s_k[n] * s_l[m]
|
|
794
|
+
|
|
795
|
+
# Update diagonal blocks
|
|
796
|
+
for n in range(3):
|
|
797
|
+
for m in range(n):
|
|
798
|
+
self.cart_hess[i * 3 + n][i * 3 + m] += t_ij * s_i[n] * s_i[m]
|
|
799
|
+
self.cart_hess[j * 3 + n][j * 3 + m] += t_ij * s_j[n] * s_j[m]
|
|
800
|
+
self.cart_hess[k * 3 + n][k * 3 + m] += t_ij * s_k[n] * s_k[m]
|
|
801
|
+
self.cart_hess[l * 3 + n][l * 3 + m] += t_ij * s_l[n] * s_l[m]
|
|
802
|
+
|
|
803
|
+
return
|
|
804
|
+
|
|
805
|
+
def calculate_three_body_term(self, coord, element_list, charges, cn):
|
|
806
|
+
"""Calculate three-body dispersion contribution to the Hessian (D4 specific)"""
|
|
807
|
+
s9 = self.d4_params.s9 # Scaling parameter for three-body term
|
|
808
|
+
if abs(s9) < 1e-12:
|
|
809
|
+
return # Skip if three-body term is turned off
|
|
810
|
+
|
|
811
|
+
n_atoms = len(coord)
|
|
812
|
+
|
|
813
|
+
# Loop over all atom triplets
|
|
814
|
+
for i in range(n_atoms):
|
|
815
|
+
for j in range(i):
|
|
816
|
+
for k in range(j):
|
|
817
|
+
# Get positions
|
|
818
|
+
r_i = coord[i]
|
|
819
|
+
r_j = coord[j]
|
|
820
|
+
r_k = coord[k]
|
|
821
|
+
|
|
822
|
+
# Calculate interatomic distances
|
|
823
|
+
r_ij = np.linalg.norm(r_i - r_j)
|
|
824
|
+
r_jk = np.linalg.norm(r_j - r_k)
|
|
825
|
+
r_ki = np.linalg.norm(r_k - r_i)
|
|
826
|
+
|
|
827
|
+
# Get coordination-number scaled C6 coefficients
|
|
828
|
+
c6_ij, _, r0_ij = self.get_d4_parameters(
|
|
829
|
+
element_list[i], element_list[j],
|
|
830
|
+
q1=charges[i], q2=charges[j],
|
|
831
|
+
cn1=cn[i], cn2=cn[j]
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
c6_jk, _, r0_jk = self.get_d4_parameters(
|
|
835
|
+
element_list[j], element_list[k],
|
|
836
|
+
q1=charges[j], q2=charges[k],
|
|
837
|
+
cn1=cn[j], cn2=cn[k]
|
|
838
|
+
)
|
|
839
|
+
|
|
840
|
+
c6_ki, _, r0_ki = self.get_d4_parameters(
|
|
841
|
+
element_list[k], element_list[i],
|
|
842
|
+
q1=charges[k], q2=charges[i],
|
|
843
|
+
cn1=cn[k], cn2=cn[i]
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
# Calculate geometric mean of C6 coefficients
|
|
847
|
+
c9 = (c6_ij * c6_jk * c6_ki) ** (1.0/3.0)
|
|
848
|
+
|
|
849
|
+
# Calculate three-body damping
|
|
850
|
+
damp_ij = self.d4_damping_function(r_ij, r0_ij)
|
|
851
|
+
damp_jk = self.d4_damping_function(r_jk, r0_jk)
|
|
852
|
+
damp_ki = self.d4_damping_function(r_ki, r0_ki)
|
|
853
|
+
damp = damp_ij * damp_jk * damp_ki
|
|
854
|
+
|
|
855
|
+
# Calculate angle factor
|
|
856
|
+
cos_ijk = np.dot(r_i - r_j, r_k - r_j) / (r_ij * r_jk)
|
|
857
|
+
cos_jki = np.dot(r_j - r_k, r_i - r_k) / (r_jk * r_ki)
|
|
858
|
+
cos_kij = np.dot(r_k - r_i, r_j - r_i) / (r_ki * r_ij)
|
|
859
|
+
angle_factor = 1.0 + 3.0 * cos_ijk * cos_jki * cos_kij
|
|
860
|
+
|
|
861
|
+
# Calculate three-body energy term
|
|
862
|
+
e_3 = s9 * angle_factor * c9 * damp / (r_ij * r_jk * r_ki) ** 3
|
|
863
|
+
|
|
864
|
+
# Calculate force constants (second derivatives)
|
|
865
|
+
# This is a simplified approximation - full D4 three-body Hessian is complex
|
|
866
|
+
fc_scale = 0.01 * s9 * angle_factor * c9 * damp
|
|
867
|
+
|
|
868
|
+
# Add approximate three-body contributions to Hessian
|
|
869
|
+
for n in range(3):
|
|
870
|
+
for m in range(3):
|
|
871
|
+
# Diagonal blocks (diagonal atoms)
|
|
872
|
+
if n == m:
|
|
873
|
+
self.cart_hess[i * 3 + n][i * 3 + m] += fc_scale / r_ij**6 + fc_scale / r_ki**6
|
|
874
|
+
self.cart_hess[j * 3 + n][j * 3 + m] += fc_scale / r_ij**6 + fc_scale / r_jk**6
|
|
875
|
+
self.cart_hess[k * 3 + n][k * 3 + m] += fc_scale / r_jk**6 + fc_scale / r_ki**6
|
|
876
|
+
|
|
877
|
+
# Off-diagonal blocks (between atoms)
|
|
878
|
+
self.cart_hess[i * 3 + n][j * 3 + m] -= fc_scale / r_ij**6
|
|
879
|
+
self.cart_hess[j * 3 + n][i * 3 + m] -= fc_scale / r_ij**6
|
|
880
|
+
|
|
881
|
+
self.cart_hess[j * 3 + n][k * 3 + m] -= fc_scale / r_jk**6
|
|
882
|
+
self.cart_hess[k * 3 + n][j * 3 + m] -= fc_scale / r_jk**6
|
|
883
|
+
|
|
884
|
+
self.cart_hess[k * 3 + n][i * 3 + m] -= fc_scale / r_ki**6
|
|
885
|
+
self.cart_hess[i * 3 + n][k * 3 + m] -= fc_scale / r_ki**6
|
|
886
|
+
|
|
887
|
+
return
|
|
888
|
+
|
|
889
|
+
def main(self, coord, element_list, cart_gradient):
|
|
890
|
+
"""Main method to calculate the approximate Hessian using Swart's model with D4 dispersion"""
|
|
891
|
+
print("Generating Swart's approximate hessian with D4 dispersion correction...")
|
|
892
|
+
self.cart_hess = np.zeros((len(coord) * 3, len(coord) * 3), dtype="float64")
|
|
893
|
+
|
|
894
|
+
# Calculate coordination numbers and atomic charges for D4
|
|
895
|
+
cn = self.calculate_coordination_numbers(coord, element_list)
|
|
896
|
+
charges = self.estimate_atomic_charges(coord, element_list)
|
|
897
|
+
|
|
898
|
+
# Calculate all contributions to the Hessian
|
|
899
|
+
self.swart_bond(coord, element_list, charges, cn)
|
|
900
|
+
self.swart_angle(coord, element_list, charges, cn)
|
|
901
|
+
self.swart_dihedral_angle(coord, element_list, charges, cn)
|
|
902
|
+
self.swart_out_of_plane(coord, element_list, charges, cn)
|
|
903
|
+
|
|
904
|
+
# Add D4-specific three-body term
|
|
905
|
+
self.calculate_three_body_term(coord, element_list, charges, cn)
|
|
906
|
+
|
|
907
|
+
# Ensure symmetry of the Hessian matrix
|
|
908
|
+
n_basis = len(coord) * 3
|
|
909
|
+
for i in range(n_basis):
|
|
910
|
+
for j in range(i):
|
|
911
|
+
if abs(self.cart_hess[i][j] - self.cart_hess[j][i]) > 1.0e-10:
|
|
912
|
+
avg = (self.cart_hess[i][j] + self.cart_hess[j][i]) / 2.0
|
|
913
|
+
self.cart_hess[i][j] = avg
|
|
914
|
+
self.cart_hess[j][i] = avg
|
|
915
|
+
|
|
916
|
+
# Project out translational and rotational modes
|
|
917
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
|
|
918
|
+
return hess_proj
|