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,1030 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib, element_number
|
|
4
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
5
|
+
from multioptpy.ModelHessian.calc_params import torsion2, outofplane2
|
|
6
|
+
from multioptpy.Parameters.parameter import D4Parameters, D2_C6_coeff_lib, UFF_VDW_distance_lib
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Lindh2007D4ApproxHessian:
|
|
10
|
+
"""
|
|
11
|
+
Lindh's Model Hessian (2007) augmented with D4 dispersion correction.
|
|
12
|
+
|
|
13
|
+
This class implements Lindh's 2007 approximate Hessian model with D4 dispersion
|
|
14
|
+
corrections for improved accuracy in describing non-covalent interactions.
|
|
15
|
+
|
|
16
|
+
References:
|
|
17
|
+
- Lindh et al., Chem. Phys. Lett. 2007, 241, 423.
|
|
18
|
+
- Caldeweyher et al., J. Chem. Phys. 2019, 150, 154122 (DFT-D4).
|
|
19
|
+
- https://github.com/grimme-lab/xtb/blob/main/src/model_hessian.f90
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self):
|
|
23
|
+
# Unit conversion constants
|
|
24
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
25
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
26
|
+
|
|
27
|
+
# Force constant parameters
|
|
28
|
+
self.bond_threshold_scale = 1.0
|
|
29
|
+
self.kr = 0.45 # Bond stretching force constant
|
|
30
|
+
self.kf = 0.10 # Angle bend force constant
|
|
31
|
+
self.kt = 0.0025 # Torsion force constant
|
|
32
|
+
self.ko = 0.16 # Out-of-plane force constant
|
|
33
|
+
self.kd = 0.05 # Dispersion force constant
|
|
34
|
+
|
|
35
|
+
# Numerical parameters
|
|
36
|
+
self.cutoff = 50.0 # Cutoff for long-range interactions (Bohr)
|
|
37
|
+
self.eps = 1.0e-12 # Numerical threshold for avoiding division by zero
|
|
38
|
+
|
|
39
|
+
# Reference parameters (element type matrices)
|
|
40
|
+
self.rAv = np.array([
|
|
41
|
+
[1.3500, 2.1000, 2.5300],
|
|
42
|
+
[2.1000, 2.8700, 3.8000],
|
|
43
|
+
[2.5300, 3.8000, 4.5000]
|
|
44
|
+
])
|
|
45
|
+
|
|
46
|
+
self.aAv = np.array([
|
|
47
|
+
[1.0000, 0.3949, 0.3949],
|
|
48
|
+
[0.3949, 0.2800, 0.1200],
|
|
49
|
+
[0.3949, 0.1200, 0.0600]
|
|
50
|
+
])
|
|
51
|
+
|
|
52
|
+
self.dAv = np.array([
|
|
53
|
+
[0.0000, 3.6000, 3.6000],
|
|
54
|
+
[3.6000, 5.3000, 5.3000],
|
|
55
|
+
[3.6000, 5.3000, 5.3000]
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
# D4 dispersion parameters
|
|
59
|
+
self.d4params = D4Parameters()
|
|
60
|
+
|
|
61
|
+
def select_idx(self, elem_num):
|
|
62
|
+
"""
|
|
63
|
+
Determine element group index for parameter selection.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
elem_num (str or int): Element symbol or atomic number
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
int: Group index (0-2) for parameter lookup
|
|
70
|
+
"""
|
|
71
|
+
if isinstance(elem_num, str):
|
|
72
|
+
elem_num = element_number(elem_num)
|
|
73
|
+
|
|
74
|
+
# Group 1: H
|
|
75
|
+
if elem_num > 0 and elem_num < 2:
|
|
76
|
+
return 0
|
|
77
|
+
# Group 2: First row elements (Li-Ne)
|
|
78
|
+
elif elem_num >= 2 and elem_num < 10:
|
|
79
|
+
return 1
|
|
80
|
+
# Group 3: All others
|
|
81
|
+
else:
|
|
82
|
+
return 2
|
|
83
|
+
|
|
84
|
+
def calc_force_const(self, alpha, r_0, distance_2):
|
|
85
|
+
"""
|
|
86
|
+
Calculate bond stretching force constant based on Lindh's model.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
alpha: Exponential parameter
|
|
90
|
+
r_0: Reference bond length
|
|
91
|
+
distance_2: Squared distance between atoms
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
float: Force constant
|
|
95
|
+
"""
|
|
96
|
+
return np.exp(alpha * (r_0**2 - distance_2))
|
|
97
|
+
|
|
98
|
+
def get_c6_coefficient(self, element):
|
|
99
|
+
"""
|
|
100
|
+
Get C6 dispersion coefficient for an element.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
element: Element symbol
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
float: C6 coefficient in atomic units
|
|
107
|
+
"""
|
|
108
|
+
return D2_C6_coeff_lib(element)
|
|
109
|
+
|
|
110
|
+
def estimate_atomic_charges(self, coord, element_list):
|
|
111
|
+
"""
|
|
112
|
+
Estimate atomic partial charges using electronegativity equilibration.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
coord: Atomic coordinates (Bohr)
|
|
116
|
+
element_list: List of element symbols
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
charges: Array of partial charges
|
|
120
|
+
"""
|
|
121
|
+
n_atoms = len(coord)
|
|
122
|
+
charges = np.zeros(n_atoms)
|
|
123
|
+
|
|
124
|
+
# Detect bonds based on distance
|
|
125
|
+
bonds = []
|
|
126
|
+
for i in range(n_atoms):
|
|
127
|
+
for j in range(i+1, n_atoms):
|
|
128
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
129
|
+
r_cov_i = covalent_radii_lib(element_list[i])
|
|
130
|
+
r_cov_j = covalent_radii_lib(element_list[j])
|
|
131
|
+
r_sum = (r_cov_i + r_cov_j) * self.bond_threshold_scale
|
|
132
|
+
|
|
133
|
+
if r_ij < r_sum * 1.3: # 1.3 is a common threshold for bond detection
|
|
134
|
+
bonds.append((i, j))
|
|
135
|
+
|
|
136
|
+
# Estimate charges based on electronegativity differences
|
|
137
|
+
for i, j in bonds:
|
|
138
|
+
en_i = self.d4params.get_electronegativity(element_list[i])
|
|
139
|
+
en_j = self.d4params.get_electronegativity(element_list[j])
|
|
140
|
+
|
|
141
|
+
# Simple electronegativity-based charge transfer
|
|
142
|
+
en_diff = en_j - en_i
|
|
143
|
+
charge_transfer = 0.1 * np.tanh(0.2 * en_diff) # Sigmoidal scaling for stability
|
|
144
|
+
|
|
145
|
+
charges[i] += charge_transfer
|
|
146
|
+
charges[j] -= charge_transfer
|
|
147
|
+
|
|
148
|
+
# Normalize to ensure total charge is zero
|
|
149
|
+
charges -= np.mean(charges)
|
|
150
|
+
|
|
151
|
+
return charges
|
|
152
|
+
|
|
153
|
+
def calculate_coordination_numbers(self, coord, element_list):
|
|
154
|
+
"""
|
|
155
|
+
Calculate atomic coordination numbers for scaling dispersion.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
coord: Atomic coordinates (Bohr)
|
|
159
|
+
element_list: List of element symbols
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
cn: Array of coordination numbers
|
|
163
|
+
"""
|
|
164
|
+
n_atoms = len(coord)
|
|
165
|
+
cn = np.zeros(n_atoms)
|
|
166
|
+
|
|
167
|
+
# D4 uses a counting function based on interatomic distances
|
|
168
|
+
for i in range(n_atoms):
|
|
169
|
+
r_cov_i = covalent_radii_lib(element_list[i])
|
|
170
|
+
|
|
171
|
+
for j in range(n_atoms):
|
|
172
|
+
if i == j:
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
r_cov_j = covalent_radii_lib(element_list[j])
|
|
176
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
177
|
+
|
|
178
|
+
# Coordination number contribution with Gaussian-like counting function
|
|
179
|
+
r_cov = r_cov_i + r_cov_j
|
|
180
|
+
k1 = 16.0 # Steepness parameter
|
|
181
|
+
cn_contrib = 1.0 / (1.0 + np.exp(-k1 * (r_cov/r_ij - 1.0)))
|
|
182
|
+
cn[i] += cn_contrib
|
|
183
|
+
|
|
184
|
+
return cn
|
|
185
|
+
|
|
186
|
+
def calc_d4_force_const(self, r_ij, c6_param, c8_param, r0_param, q_scaling=1.0):
|
|
187
|
+
"""
|
|
188
|
+
Calculate D4 dispersion force constant with Becke-Johnson damping and charge scaling.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
r_ij: Distance between atoms
|
|
192
|
+
c6_param: C6 dispersion coefficient
|
|
193
|
+
c8_param: C8 dispersion coefficient
|
|
194
|
+
r0_param: van der Waals radius sum parameter
|
|
195
|
+
q_scaling: Charge-dependent scaling factor
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
float: D4 dispersion force constant
|
|
199
|
+
"""
|
|
200
|
+
# Becke-Johnson damping function for C6 term
|
|
201
|
+
r0_plus_a1 = r0_param + self.d4params.a1
|
|
202
|
+
f_damp_6 = r_ij**6 / (r_ij**6 + (r0_plus_a1 * self.d4params.a2)**6)
|
|
203
|
+
|
|
204
|
+
# Becke-Johnson damping function for C8 term
|
|
205
|
+
f_damp_8 = r_ij**8 / (r_ij**8 + (r0_plus_a1 * self.d4params.a2)**8)
|
|
206
|
+
|
|
207
|
+
# Apply charge scaling to C6 and C8 coefficients
|
|
208
|
+
c6_scaled = c6_param * q_scaling
|
|
209
|
+
c8_scaled = c8_param * q_scaling
|
|
210
|
+
|
|
211
|
+
# D4 dispersion energy contributions
|
|
212
|
+
e6 = -self.d4params.s6 * c6_scaled * f_damp_6 / r_ij**6
|
|
213
|
+
e8 = -self.d4params.s8 * c8_scaled * f_damp_8 / r_ij**8
|
|
214
|
+
|
|
215
|
+
# Combined force constant (negative of energy for attractive contribution)
|
|
216
|
+
return -(e6 + e8)
|
|
217
|
+
|
|
218
|
+
def get_d4_parameters(self, elem1, elem2, q1=0.0, q2=0.0):
|
|
219
|
+
"""
|
|
220
|
+
Get D4 parameters for a pair of elements with charge scaling.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
elem1: First element symbol
|
|
224
|
+
elem2: Second element symbol
|
|
225
|
+
q1: Partial charge on first atom
|
|
226
|
+
q2: Partial charge on second atom
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
tuple: (c6_param, c8_param, r0_param, q_scaling) for the element pair
|
|
230
|
+
"""
|
|
231
|
+
# Get reference polarizabilities
|
|
232
|
+
alpha_1 = self.d4params.get_polarizability(elem1)
|
|
233
|
+
alpha_2 = self.d4params.get_polarizability(elem2)
|
|
234
|
+
|
|
235
|
+
# Get base C6 coefficients
|
|
236
|
+
c6_1 = self.get_c6_coefficient(elem1)
|
|
237
|
+
c6_2 = self.get_c6_coefficient(elem2)
|
|
238
|
+
|
|
239
|
+
# Combine C6 coefficients with Casimir-Polder formula
|
|
240
|
+
c6_param = 2.0 * c6_1 * c6_2 / (c6_1 + c6_2)
|
|
241
|
+
|
|
242
|
+
# Get r4r2 values for C8 coefficient calculation
|
|
243
|
+
r4r2_1 = self.d4params.get_r4r2(elem1)
|
|
244
|
+
r4r2_2 = self.d4params.get_r4r2(elem2)
|
|
245
|
+
|
|
246
|
+
# Calculate C8 coefficient using D4 formula
|
|
247
|
+
c8_param = 3.0 * c6_param * np.sqrt(r4r2_1 * r4r2_2)
|
|
248
|
+
|
|
249
|
+
# Calculate R0 parameter (vdW radii sum)
|
|
250
|
+
r0_1 = UFF_VDW_distance_lib(elem1) / self.bohr2angstroms
|
|
251
|
+
r0_2 = UFF_VDW_distance_lib(elem2) / self.bohr2angstroms
|
|
252
|
+
r0_param = r0_1 + r0_2
|
|
253
|
+
|
|
254
|
+
# Apply charge scaling (D4-specific feature)
|
|
255
|
+
# Gaussian charge scaling function
|
|
256
|
+
q_scaling = np.exp(-self.d4params.ga * (q1**2 + q2**2))
|
|
257
|
+
|
|
258
|
+
return c6_param, c8_param, r0_param, q_scaling
|
|
259
|
+
|
|
260
|
+
def calc_d4_gradient_components(self, x_ij, y_ij, z_ij, c6_param, c8_param, r0_param, q_scaling=1.0):
|
|
261
|
+
"""
|
|
262
|
+
Calculate D4 dispersion gradient components.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
x_ij, y_ij, z_ij: Distance components
|
|
266
|
+
c6_param: C6 dispersion coefficient
|
|
267
|
+
c8_param: C8 dispersion coefficient
|
|
268
|
+
r0_param: van der Waals radius sum parameter
|
|
269
|
+
q_scaling: Charge-dependent scaling factor
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
tuple: (xx, xy, xz, yy, yz, zz) gradient components
|
|
273
|
+
"""
|
|
274
|
+
r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
|
|
275
|
+
r_ij = np.sqrt(r_ij_2)
|
|
276
|
+
|
|
277
|
+
if r_ij < 0.1: # Avoid numerical issues
|
|
278
|
+
return 0.0, 0.0, 0.0, 0.0, 0.0, 0.0
|
|
279
|
+
|
|
280
|
+
# BJ damping parameters
|
|
281
|
+
r0_plus_a1 = r0_param + self.d4params.a1
|
|
282
|
+
a2_term = self.d4params.a2
|
|
283
|
+
bj_term_6 = (r0_plus_a1 * a2_term)**6
|
|
284
|
+
bj_term_8 = (r0_plus_a1 * a2_term)**8
|
|
285
|
+
|
|
286
|
+
# Calculate damping functions and their derivatives
|
|
287
|
+
r_ij_6 = r_ij**6
|
|
288
|
+
r_ij_8 = r_ij**8
|
|
289
|
+
|
|
290
|
+
# C6 term: damping and derivatives
|
|
291
|
+
damp_6 = r_ij_6 / (r_ij_6 + bj_term_6)
|
|
292
|
+
d_damp_6_dr = 6.0 * r_ij_6 * bj_term_6 / ((r_ij_6 + bj_term_6)**2 * r_ij)
|
|
293
|
+
|
|
294
|
+
# C8 term: damping and derivatives
|
|
295
|
+
damp_8 = r_ij_8 / (r_ij_8 + bj_term_8)
|
|
296
|
+
d_damp_8_dr = 8.0 * r_ij_8 * bj_term_8 / ((r_ij_8 + bj_term_8)**2 * r_ij)
|
|
297
|
+
|
|
298
|
+
# Apply charge scaling
|
|
299
|
+
c6_scaled = c6_param * q_scaling
|
|
300
|
+
c8_scaled = c8_param * q_scaling
|
|
301
|
+
|
|
302
|
+
# Force (negative derivative of energy)
|
|
303
|
+
f6 = self.d4params.s6 * c6_scaled * (6.0 * damp_6 / r_ij**7 + d_damp_6_dr / r_ij**6)
|
|
304
|
+
f8 = self.d4params.s8 * c8_scaled * (8.0 * damp_8 / r_ij**9 + d_damp_8_dr / r_ij**8)
|
|
305
|
+
|
|
306
|
+
# Total force
|
|
307
|
+
force = f6 + f8
|
|
308
|
+
|
|
309
|
+
# Calculate derivative components
|
|
310
|
+
deriv_scale = force / r_ij
|
|
311
|
+
|
|
312
|
+
# Calculate gradient components
|
|
313
|
+
xx = deriv_scale * x_ij**2 / r_ij_2
|
|
314
|
+
xy = deriv_scale * x_ij * y_ij / r_ij_2
|
|
315
|
+
xz = deriv_scale * x_ij * z_ij / r_ij_2
|
|
316
|
+
yy = deriv_scale * y_ij**2 / r_ij_2
|
|
317
|
+
yz = deriv_scale * y_ij * z_ij / r_ij_2
|
|
318
|
+
zz = deriv_scale * z_ij**2 / r_ij_2
|
|
319
|
+
|
|
320
|
+
return xx, xy, xz, yy, yz, zz
|
|
321
|
+
|
|
322
|
+
def lindh2007_bond(self, coord, element_list, charges, cn):
|
|
323
|
+
"""
|
|
324
|
+
Calculate bond stretching contributions to the Hessian.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
coord: Atomic coordinates (Bohr)
|
|
328
|
+
element_list: List of element symbols
|
|
329
|
+
charges: Atomic partial charges
|
|
330
|
+
cn: Coordination numbers
|
|
331
|
+
"""
|
|
332
|
+
n_atoms = len(coord)
|
|
333
|
+
|
|
334
|
+
for i in range(n_atoms):
|
|
335
|
+
i_idx = self.select_idx(element_list[i])
|
|
336
|
+
|
|
337
|
+
for j in range(i):
|
|
338
|
+
j_idx = self.select_idx(element_list[j])
|
|
339
|
+
|
|
340
|
+
# Calculate distance components and magnitude
|
|
341
|
+
x_ij = coord[i][0] - coord[j][0]
|
|
342
|
+
y_ij = coord[i][1] - coord[j][1]
|
|
343
|
+
z_ij = coord[i][2] - coord[j][2]
|
|
344
|
+
r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
|
|
345
|
+
r_ij = np.sqrt(r_ij_2)
|
|
346
|
+
|
|
347
|
+
# Get Lindh parameters
|
|
348
|
+
r_0 = self.rAv[i_idx][j_idx]
|
|
349
|
+
d_0 = self.dAv[i_idx][j_idx]
|
|
350
|
+
alpha = self.aAv[i_idx][j_idx]
|
|
351
|
+
|
|
352
|
+
# Determine appropriate bond length based on bond type
|
|
353
|
+
single_bond = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
354
|
+
covalent_length = single_bond # Default to single bond
|
|
355
|
+
|
|
356
|
+
# Get D4 parameters with charge scaling
|
|
357
|
+
c6_param, c8_param, r0_param, q_scaling = self.get_d4_parameters(
|
|
358
|
+
element_list[i], element_list[j], charges[i], charges[j])
|
|
359
|
+
|
|
360
|
+
# Calculate force constants
|
|
361
|
+
lindh_force = self.kr * self.calc_force_const(alpha, covalent_length, r_ij_2)
|
|
362
|
+
|
|
363
|
+
# Add D4 dispersion if atoms are far apart
|
|
364
|
+
d4_factor = 0.0
|
|
365
|
+
if r_ij > 2.0 * covalent_length:
|
|
366
|
+
d4_factor = self.kd * self.calc_d4_force_const(r_ij, c6_param, c8_param, r0_param, q_scaling)
|
|
367
|
+
|
|
368
|
+
# Combined force constant
|
|
369
|
+
g_mm = lindh_force + d4_factor
|
|
370
|
+
|
|
371
|
+
# Calculate D4 gradient components
|
|
372
|
+
d4_xx, d4_xy, d4_xz, d4_yy, d4_yz, d4_zz = self.calc_d4_gradient_components(
|
|
373
|
+
x_ij, y_ij, z_ij, c6_param, c8_param, r0_param, q_scaling)
|
|
374
|
+
|
|
375
|
+
# Calculate Hessian elements
|
|
376
|
+
hess_xx = g_mm * x_ij**2 / r_ij_2 - d4_xx
|
|
377
|
+
hess_xy = g_mm * x_ij * y_ij / r_ij_2 - d4_xy
|
|
378
|
+
hess_xz = g_mm * x_ij * z_ij / r_ij_2 - d4_xz
|
|
379
|
+
hess_yy = g_mm * y_ij**2 / r_ij_2 - d4_yy
|
|
380
|
+
hess_yz = g_mm * y_ij * z_ij / r_ij_2 - d4_yz
|
|
381
|
+
hess_zz = g_mm * z_ij**2 / r_ij_2 - d4_zz
|
|
382
|
+
|
|
383
|
+
# Update diagonal blocks
|
|
384
|
+
i_offset = i * 3
|
|
385
|
+
j_offset = j * 3
|
|
386
|
+
|
|
387
|
+
# i-i block
|
|
388
|
+
self.cart_hess[i_offset, i_offset] += hess_xx
|
|
389
|
+
self.cart_hess[i_offset + 1, i_offset] += hess_xy
|
|
390
|
+
self.cart_hess[i_offset + 1, i_offset + 1] += hess_yy
|
|
391
|
+
self.cart_hess[i_offset + 2, i_offset] += hess_xz
|
|
392
|
+
self.cart_hess[i_offset + 2, i_offset + 1] += hess_yz
|
|
393
|
+
self.cart_hess[i_offset + 2, i_offset + 2] += hess_zz
|
|
394
|
+
|
|
395
|
+
# j-j block
|
|
396
|
+
self.cart_hess[j_offset, j_offset] += hess_xx
|
|
397
|
+
self.cart_hess[j_offset + 1, j_offset] += hess_xy
|
|
398
|
+
self.cart_hess[j_offset + 1, j_offset + 1] += hess_yy
|
|
399
|
+
self.cart_hess[j_offset + 2, j_offset] += hess_xz
|
|
400
|
+
self.cart_hess[j_offset + 2, j_offset + 1] += hess_yz
|
|
401
|
+
self.cart_hess[j_offset + 2, j_offset + 2] += hess_zz
|
|
402
|
+
|
|
403
|
+
# i-j block
|
|
404
|
+
self.cart_hess[i_offset, j_offset] -= hess_xx
|
|
405
|
+
self.cart_hess[i_offset, j_offset + 1] -= hess_xy
|
|
406
|
+
self.cart_hess[i_offset, j_offset + 2] -= hess_xz
|
|
407
|
+
self.cart_hess[i_offset + 1, j_offset] -= hess_xy
|
|
408
|
+
self.cart_hess[i_offset + 1, j_offset + 1] -= hess_yy
|
|
409
|
+
self.cart_hess[i_offset + 1, j_offset + 2] -= hess_yz
|
|
410
|
+
self.cart_hess[i_offset + 2, j_offset] -= hess_xz
|
|
411
|
+
self.cart_hess[i_offset + 2, j_offset + 1] -= hess_yz
|
|
412
|
+
self.cart_hess[i_offset + 2, j_offset + 2] -= hess_zz
|
|
413
|
+
|
|
414
|
+
def lindh2007_angle(self, coord, element_list, charges, cn):
|
|
415
|
+
"""
|
|
416
|
+
Calculate angle bending contributions to the Hessian with D4 dispersion.
|
|
417
|
+
|
|
418
|
+
Args:
|
|
419
|
+
coord: Atomic coordinates (Bohr)
|
|
420
|
+
element_list: List of element symbols
|
|
421
|
+
charges: Atomic partial charges
|
|
422
|
+
cn: Coordination numbers
|
|
423
|
+
"""
|
|
424
|
+
n_atoms = len(coord)
|
|
425
|
+
|
|
426
|
+
for i in range(n_atoms):
|
|
427
|
+
i_idx = self.select_idx(element_list[i])
|
|
428
|
+
|
|
429
|
+
for j in range(n_atoms):
|
|
430
|
+
if i == j:
|
|
431
|
+
continue
|
|
432
|
+
|
|
433
|
+
j_idx = self.select_idx(element_list[j])
|
|
434
|
+
|
|
435
|
+
# Vector from j to i
|
|
436
|
+
x_ij = coord[i][0] - coord[j][0]
|
|
437
|
+
y_ij = coord[i][1] - coord[j][1]
|
|
438
|
+
z_ij = coord[i][2] - coord[j][2]
|
|
439
|
+
r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
|
|
440
|
+
r_ij = np.sqrt(r_ij_2)
|
|
441
|
+
|
|
442
|
+
# Get bond parameters
|
|
443
|
+
covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
444
|
+
|
|
445
|
+
# Get Lindh parameters
|
|
446
|
+
r_ij_0 = self.rAv[i_idx][j_idx]
|
|
447
|
+
d_ij_0 = self.dAv[i_idx][j_idx]
|
|
448
|
+
alpha_ij = self.aAv[i_idx][j_idx]
|
|
449
|
+
|
|
450
|
+
# Loop through potential third atoms to form an angle
|
|
451
|
+
for k in range(j):
|
|
452
|
+
if i == k:
|
|
453
|
+
continue
|
|
454
|
+
|
|
455
|
+
k_idx = self.select_idx(element_list[k])
|
|
456
|
+
|
|
457
|
+
# Get parameters for i-k interaction
|
|
458
|
+
r_ik_0 = self.rAv[i_idx][k_idx]
|
|
459
|
+
d_ik_0 = self.dAv[i_idx][k_idx]
|
|
460
|
+
alpha_ik = self.aAv[i_idx][k_idx]
|
|
461
|
+
|
|
462
|
+
# Vector from k to i
|
|
463
|
+
x_ik = coord[i][0] - coord[k][0]
|
|
464
|
+
y_ik = coord[i][1] - coord[k][1]
|
|
465
|
+
z_ik = coord[i][2] - coord[k][2]
|
|
466
|
+
r_ik_2 = x_ik**2 + y_ik**2 + z_ik**2
|
|
467
|
+
r_ik = np.sqrt(r_ik_2)
|
|
468
|
+
|
|
469
|
+
# Get bond parameters
|
|
470
|
+
covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
471
|
+
|
|
472
|
+
# Check if angle is well-defined (not linear)
|
|
473
|
+
cos_angle = (x_ij * x_ik + y_ij * y_ik + z_ij * z_ik) / (r_ij * r_ik)
|
|
474
|
+
|
|
475
|
+
if abs(cos_angle - 1.0) < self.eps:
|
|
476
|
+
continue # Skip near-linear angles
|
|
477
|
+
|
|
478
|
+
# Vector from k to j
|
|
479
|
+
x_jk = coord[j][0] - coord[k][0]
|
|
480
|
+
y_jk = coord[j][1] - coord[k][1]
|
|
481
|
+
z_jk = coord[j][2] - coord[k][2]
|
|
482
|
+
r_jk_2 = x_jk**2 + y_jk**2 + z_jk**2
|
|
483
|
+
r_jk = np.sqrt(r_jk_2)
|
|
484
|
+
|
|
485
|
+
# Calculate force constants with D4 contributions
|
|
486
|
+
c6_ij, c8_ij, r0_ij, q_ij = self.get_d4_parameters(
|
|
487
|
+
element_list[i], element_list[j], charges[i], charges[j])
|
|
488
|
+
c6_ik, c8_ik, r0_ik, q_ik = self.get_d4_parameters(
|
|
489
|
+
element_list[i], element_list[k], charges[i], charges[k])
|
|
490
|
+
|
|
491
|
+
g_ij = self.calc_force_const(alpha_ij, covalent_length_ij, r_ij_2)
|
|
492
|
+
if r_ij > 2.0 * covalent_length_ij:
|
|
493
|
+
g_ij += 0.5 * self.kd * self.calc_d4_force_const(r_ij, c6_ij, c8_ij, r0_ij, q_ij)
|
|
494
|
+
|
|
495
|
+
g_ik = self.calc_force_const(alpha_ik, covalent_length_ik, r_ik_2)
|
|
496
|
+
if r_ik > 2.0 * covalent_length_ik:
|
|
497
|
+
g_ik += 0.5 * self.kd * self.calc_d4_force_const(r_ik, c6_ik, c8_ik, r0_ik, q_ik)
|
|
498
|
+
|
|
499
|
+
# Angular force constant
|
|
500
|
+
g_jk = self.kf * (g_ij + 0.5 * self.kd / self.kr * d_ij_0) * (g_ik + 0.5 * self.kd / self.kr * d_ik_0)
|
|
501
|
+
|
|
502
|
+
# Cross product magnitude for sin(theta)
|
|
503
|
+
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
|
|
504
|
+
r_cross = np.sqrt(r_cross_2) if r_cross_2 > 1.0e-12 else 0.0
|
|
505
|
+
|
|
506
|
+
# Skip if distances are too small
|
|
507
|
+
if r_ik <= self.eps or r_ij <= self.eps or r_jk <= self.eps:
|
|
508
|
+
continue
|
|
509
|
+
|
|
510
|
+
# Calculate angle and its derivatives
|
|
511
|
+
dot_product = x_ij * x_ik + y_ij * y_ik + z_ij * z_ik
|
|
512
|
+
cos_theta = dot_product / (r_ij * r_ik)
|
|
513
|
+
sin_theta = r_cross / (r_ij * r_ik)
|
|
514
|
+
|
|
515
|
+
if sin_theta > self.eps: # Non-linear case
|
|
516
|
+
# Calculate derivatives
|
|
517
|
+
s_xj = (x_ij / r_ij * cos_theta - x_ik / r_ik) / (r_ij * sin_theta)
|
|
518
|
+
s_yj = (y_ij / r_ij * cos_theta - y_ik / r_ik) / (r_ij * sin_theta)
|
|
519
|
+
s_zj = (z_ij / r_ij * cos_theta - z_ik / r_ik) / (r_ij * sin_theta)
|
|
520
|
+
|
|
521
|
+
s_xk = (x_ik / r_ik * cos_theta - x_ij / r_ij) / (r_ik * sin_theta)
|
|
522
|
+
s_yk = (y_ik / r_ik * cos_theta - y_ij / r_ij) / (r_ik * sin_theta)
|
|
523
|
+
s_zk = (z_ik / r_ik * cos_theta - z_ij / r_ij) / (r_ik * sin_theta)
|
|
524
|
+
|
|
525
|
+
s_xi = -s_xj - s_xk
|
|
526
|
+
s_yi = -s_yj - s_yk
|
|
527
|
+
s_zi = -s_zj - s_zk
|
|
528
|
+
|
|
529
|
+
s_i = [s_xi, s_yi, s_zi]
|
|
530
|
+
s_j = [s_xj, s_yj, s_zj]
|
|
531
|
+
s_k = [s_xk, s_yk, s_zk]
|
|
532
|
+
|
|
533
|
+
# Update Hessian elements
|
|
534
|
+
for l in range(3):
|
|
535
|
+
for m in range(3):
|
|
536
|
+
# i-j block
|
|
537
|
+
if i > j:
|
|
538
|
+
self.cart_hess[i*3+l, j*3+m] += g_jk * s_i[l] * s_j[m]
|
|
539
|
+
else:
|
|
540
|
+
self.cart_hess[j*3+l, i*3+m] += g_jk * s_j[l] * s_i[m]
|
|
541
|
+
|
|
542
|
+
# i-k block
|
|
543
|
+
if i > k:
|
|
544
|
+
self.cart_hess[i*3+l, k*3+m] += g_jk * s_i[l] * s_k[m]
|
|
545
|
+
else:
|
|
546
|
+
self.cart_hess[k*3+l, i*3+m] += g_jk * s_k[l] * s_i[m]
|
|
547
|
+
|
|
548
|
+
# j-k block
|
|
549
|
+
if j > k:
|
|
550
|
+
self.cart_hess[j*3+l, k*3+m] += g_jk * s_j[l] * s_k[m]
|
|
551
|
+
else:
|
|
552
|
+
self.cart_hess[k*3+l, j*3+m] += g_jk * s_k[l] * s_j[m]
|
|
553
|
+
|
|
554
|
+
# Diagonal blocks
|
|
555
|
+
for l in range(3):
|
|
556
|
+
for m in range(l):
|
|
557
|
+
self.cart_hess[j*3+l, j*3+m] += g_jk * s_j[l] * s_j[m]
|
|
558
|
+
self.cart_hess[i*3+l, i*3+m] += g_jk * s_i[l] * s_i[m]
|
|
559
|
+
self.cart_hess[k*3+l, k*3+m] += g_jk * s_k[l] * s_k[m]
|
|
560
|
+
|
|
561
|
+
else: # Linear case
|
|
562
|
+
# Handle linear angles using arbitrary perpendicular vectors
|
|
563
|
+
if abs(y_ij) < self.eps and abs(z_ij) < self.eps:
|
|
564
|
+
x_1, y_1, z_1 = -y_ij, x_ij, 0.0
|
|
565
|
+
x_2, y_2, z_2 = -x_ij * z_ij, -y_ij * z_ij, x_ij**2 + y_ij**2
|
|
566
|
+
else:
|
|
567
|
+
x_1, y_1, z_1 = 1.0, 0.0, 0.0
|
|
568
|
+
x_2, y_2, z_2 = 0.0, 1.0, 0.0
|
|
569
|
+
|
|
570
|
+
x = [x_1, x_2]
|
|
571
|
+
y = [y_1, y_2]
|
|
572
|
+
z = [z_1, z_2]
|
|
573
|
+
|
|
574
|
+
# Calculate derivatives for two perpendicular directions
|
|
575
|
+
for ii in range(2):
|
|
576
|
+
r_1 = np.sqrt(x[ii]**2 + y[ii]**2 + z[ii]**2)
|
|
577
|
+
cos_theta_x = x[ii] / r_1
|
|
578
|
+
cos_theta_y = y[ii] / r_1
|
|
579
|
+
cos_theta_z = z[ii] / r_1
|
|
580
|
+
|
|
581
|
+
# Derivatives
|
|
582
|
+
s_xj = -cos_theta_x / r_ij
|
|
583
|
+
s_yj = -cos_theta_y / r_ij
|
|
584
|
+
s_zj = -cos_theta_z / r_ij
|
|
585
|
+
s_xk = -cos_theta_x / r_ik
|
|
586
|
+
s_yk = -cos_theta_y / r_ik
|
|
587
|
+
s_zk = -cos_theta_z / r_ik
|
|
588
|
+
|
|
589
|
+
s_xi = -s_xj - s_xk
|
|
590
|
+
s_yi = -s_yj - s_yk
|
|
591
|
+
s_zi = -s_zj - s_zk
|
|
592
|
+
|
|
593
|
+
s_i = [s_xi, s_yi, s_zi]
|
|
594
|
+
s_j = [s_xj, s_yj, s_zj]
|
|
595
|
+
s_k = [s_xk, s_yk, s_zk]
|
|
596
|
+
|
|
597
|
+
# Update Hessian elements
|
|
598
|
+
for l in range(3):
|
|
599
|
+
for m in range(3):
|
|
600
|
+
# i-j block
|
|
601
|
+
if i > j:
|
|
602
|
+
self.cart_hess[i*3+l, j*3+m] += g_jk * s_i[l] * s_j[m]
|
|
603
|
+
else:
|
|
604
|
+
self.cart_hess[j*3+l, i*3+m] += g_jk * s_j[l] * s_i[m]
|
|
605
|
+
|
|
606
|
+
# i-k block
|
|
607
|
+
if i > k:
|
|
608
|
+
self.cart_hess[i*3+l, k*3+m] += g_jk * s_i[l] * s_k[m]
|
|
609
|
+
else:
|
|
610
|
+
self.cart_hess[k*3+l, i*3+m] += g_jk * s_k[l] * s_i[m]
|
|
611
|
+
|
|
612
|
+
# j-k block
|
|
613
|
+
if j > k:
|
|
614
|
+
self.cart_hess[j*3+l, k*3+m] += g_jk * s_j[l] * s_k[m]
|
|
615
|
+
else:
|
|
616
|
+
self.cart_hess[k*3+l, j*3+m] += g_jk * s_k[l] * s_j[m]
|
|
617
|
+
|
|
618
|
+
# Diagonal blocks
|
|
619
|
+
for l in range(3):
|
|
620
|
+
for m in range(l):
|
|
621
|
+
self.cart_hess[j*3+l, j*3+m] += g_jk * s_j[l] * s_j[m]
|
|
622
|
+
self.cart_hess[i*3+l, i*3+m] += g_jk * s_i[l] * s_i[m]
|
|
623
|
+
self.cart_hess[k*3+l, k*3+m] += g_jk * s_k[l] * s_k[m]
|
|
624
|
+
|
|
625
|
+
def lindh2007_dihedral_angle(self, coord, element_list, charges, cn):
|
|
626
|
+
"""
|
|
627
|
+
Calculate dihedral angle (torsion) contributions to the Hessian with D4 dispersion.
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
coord: Atomic coordinates (Bohr)
|
|
631
|
+
element_list: List of element symbols
|
|
632
|
+
charges: Atomic partial charges
|
|
633
|
+
cn: Coordination numbers
|
|
634
|
+
"""
|
|
635
|
+
n_atoms = len(coord)
|
|
636
|
+
|
|
637
|
+
for j in range(n_atoms):
|
|
638
|
+
t_xyz_2 = coord[j]
|
|
639
|
+
|
|
640
|
+
for k in range(j+1, n_atoms):
|
|
641
|
+
t_xyz_3 = coord[k]
|
|
642
|
+
|
|
643
|
+
for i in range(j):
|
|
644
|
+
if i == k:
|
|
645
|
+
continue
|
|
646
|
+
|
|
647
|
+
t_xyz_1 = coord[i]
|
|
648
|
+
|
|
649
|
+
for l in range(k+1, n_atoms):
|
|
650
|
+
if l == i or l == j:
|
|
651
|
+
continue
|
|
652
|
+
|
|
653
|
+
t_xyz_4 = coord[l]
|
|
654
|
+
|
|
655
|
+
# Get element indices for parameter lookup
|
|
656
|
+
i_idx = self.select_idx(element_list[i])
|
|
657
|
+
j_idx = self.select_idx(element_list[j])
|
|
658
|
+
k_idx = self.select_idx(element_list[k])
|
|
659
|
+
l_idx = self.select_idx(element_list[l])
|
|
660
|
+
|
|
661
|
+
# Get Lindh parameters
|
|
662
|
+
r_ij_0 = self.rAv[i_idx][j_idx]
|
|
663
|
+
d_ij_0 = self.dAv[i_idx][j_idx]
|
|
664
|
+
alpha_ij = self.aAv[i_idx][j_idx]
|
|
665
|
+
|
|
666
|
+
r_jk_0 = self.rAv[j_idx][k_idx]
|
|
667
|
+
d_jk_0 = self.dAv[j_idx][k_idx]
|
|
668
|
+
alpha_jk = self.aAv[j_idx][k_idx]
|
|
669
|
+
|
|
670
|
+
r_kl_0 = self.rAv[k_idx][l_idx]
|
|
671
|
+
d_kl_0 = self.dAv[k_idx][l_idx]
|
|
672
|
+
alpha_kl = self.aAv[k_idx][l_idx]
|
|
673
|
+
|
|
674
|
+
# Calculate bond vectors and lengths
|
|
675
|
+
r_ij = coord[i] - coord[j]
|
|
676
|
+
r_jk = coord[j] - coord[k]
|
|
677
|
+
r_kl = coord[k] - coord[l]
|
|
678
|
+
|
|
679
|
+
# Get bond parameters
|
|
680
|
+
covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
681
|
+
covalent_length_jk = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
682
|
+
covalent_length_kl = covalent_radii_lib(element_list[k]) + covalent_radii_lib(element_list[l])
|
|
683
|
+
|
|
684
|
+
# Calculate squared distances
|
|
685
|
+
r_ij_2 = np.sum(r_ij**2)
|
|
686
|
+
r_jk_2 = np.sum(r_jk**2)
|
|
687
|
+
r_kl_2 = np.sum(r_kl**2)
|
|
688
|
+
|
|
689
|
+
# Calculate norms
|
|
690
|
+
norm_r_ij = np.sqrt(r_ij_2)
|
|
691
|
+
norm_r_jk = np.sqrt(r_jk_2)
|
|
692
|
+
norm_r_kl = np.sqrt(r_kl_2)
|
|
693
|
+
|
|
694
|
+
# Check if near-linear angles would cause numerical issues
|
|
695
|
+
a35 = (35.0/180) * np.pi
|
|
696
|
+
cosfi_max = np.cos(a35)
|
|
697
|
+
|
|
698
|
+
cosfi2 = np.dot(r_ij, r_jk) / np.sqrt(r_ij_2 * r_jk_2)
|
|
699
|
+
if abs(cosfi2) > cosfi_max:
|
|
700
|
+
continue
|
|
701
|
+
|
|
702
|
+
cosfi3 = np.dot(r_kl, r_jk) / np.sqrt(r_kl_2 * r_jk_2)
|
|
703
|
+
if abs(cosfi3) > cosfi_max:
|
|
704
|
+
continue
|
|
705
|
+
|
|
706
|
+
# Get D4 parameters for bond pairs with charge scaling
|
|
707
|
+
c6_ij, c8_ij, r0_ij, q_ij = self.get_d4_parameters(
|
|
708
|
+
element_list[i], element_list[j], charges[i], charges[j])
|
|
709
|
+
c6_jk, c8_jk, r0_jk, q_jk = self.get_d4_parameters(
|
|
710
|
+
element_list[j], element_list[k], charges[j], charges[k])
|
|
711
|
+
c6_kl, c8_kl, r0_kl, q_kl = self.get_d4_parameters(
|
|
712
|
+
element_list[k], element_list[l], charges[k], charges[l])
|
|
713
|
+
|
|
714
|
+
# Calculate force constants with D4 contributions
|
|
715
|
+
g_ij = self.calc_force_const(alpha_ij, covalent_length_ij, r_ij_2)
|
|
716
|
+
if norm_r_ij > 2.0 * covalent_length_ij:
|
|
717
|
+
g_ij += 0.5 * self.kd * self.calc_d4_force_const(
|
|
718
|
+
norm_r_ij, c6_ij, c8_ij, r0_ij, q_ij)
|
|
719
|
+
|
|
720
|
+
g_jk = self.calc_force_const(alpha_jk, covalent_length_jk, r_jk_2)
|
|
721
|
+
if norm_r_jk > 2.0 * covalent_length_jk:
|
|
722
|
+
g_jk += 0.5 * self.kd * self.calc_d4_force_const(
|
|
723
|
+
norm_r_jk, c6_jk, c8_jk, r0_jk, q_jk)
|
|
724
|
+
|
|
725
|
+
g_kl = self.calc_force_const(alpha_kl, covalent_length_kl, r_kl_2)
|
|
726
|
+
if norm_r_kl > 2.0 * covalent_length_kl:
|
|
727
|
+
g_kl += 0.5 * self.kd * self.calc_d4_force_const(
|
|
728
|
+
norm_r_kl, c6_kl, c8_kl, r0_kl, q_kl)
|
|
729
|
+
|
|
730
|
+
# Calculate torsion force constant
|
|
731
|
+
t_ij = self.kt * (g_ij * 0.5 * self.kd / self.kr * d_ij_0) * \
|
|
732
|
+
(g_jk * 0.5 * self.kd / self.kr * d_jk_0) * \
|
|
733
|
+
(g_kl * 0.5 * self.kd / self.kr * d_kl_0)
|
|
734
|
+
|
|
735
|
+
# Calculate torsion angle and derivatives
|
|
736
|
+
t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
|
|
737
|
+
tau, c = torsion2(t_xyz)
|
|
738
|
+
|
|
739
|
+
# Extract derivatives
|
|
740
|
+
s_i = c[0]
|
|
741
|
+
s_j = c[1]
|
|
742
|
+
s_k = c[2]
|
|
743
|
+
s_l = c[3]
|
|
744
|
+
|
|
745
|
+
# Apply coordination number scaling (D4-specific)
|
|
746
|
+
# Torsions involving sp3 centers get higher weight
|
|
747
|
+
cn_scaling = 1.0
|
|
748
|
+
if 3.9 <= cn[j] <= 4.1 and 3.9 <= cn[k] <= 4.1: # Both sp3
|
|
749
|
+
cn_scaling = 1.2
|
|
750
|
+
elif (cn[j] <= 3.1 and cn[k] <= 3.1): # Both sp2 or lower
|
|
751
|
+
cn_scaling = 0.8
|
|
752
|
+
|
|
753
|
+
# Apply scaling to force constant
|
|
754
|
+
t_ij *= cn_scaling
|
|
755
|
+
|
|
756
|
+
# Update off-diagonal blocks
|
|
757
|
+
for n in range(3):
|
|
758
|
+
for m in range(3):
|
|
759
|
+
self.cart_hess[3*i+n, 3*j+m] += t_ij * s_i[n] * s_j[m]
|
|
760
|
+
self.cart_hess[3*i+n, 3*k+m] += t_ij * s_i[n] * s_k[m]
|
|
761
|
+
self.cart_hess[3*i+n, 3*l+m] += t_ij * s_i[n] * s_l[m]
|
|
762
|
+
self.cart_hess[3*j+n, 3*k+m] += t_ij * s_j[n] * s_k[m]
|
|
763
|
+
self.cart_hess[3*j+n, 3*l+m] += t_ij * s_j[n] * s_l[m]
|
|
764
|
+
self.cart_hess[3*k+n, 3*l+m] += t_ij * s_k[n] * s_l[m]
|
|
765
|
+
|
|
766
|
+
# Update diagonal blocks (lower triangle)
|
|
767
|
+
for n in range(3):
|
|
768
|
+
for m in range(n):
|
|
769
|
+
self.cart_hess[3*i+n, 3*i+m] += t_ij * s_i[n] * s_i[m]
|
|
770
|
+
self.cart_hess[3*j+n, 3*j+m] += t_ij * s_j[n] * s_j[m]
|
|
771
|
+
self.cart_hess[3*k+n, 3*k+m] += t_ij * s_k[n] * s_k[m]
|
|
772
|
+
self.cart_hess[3*l+n, 3*l+m] += t_ij * s_l[n] * s_l[m]
|
|
773
|
+
|
|
774
|
+
def lindh2007_out_of_plane(self, coord, element_list, charges, cn):
|
|
775
|
+
"""
|
|
776
|
+
Calculate out-of-plane bending contributions to the Hessian with D4 dispersion.
|
|
777
|
+
|
|
778
|
+
Args:
|
|
779
|
+
coord: Atomic coordinates (Bohr)
|
|
780
|
+
element_list: List of element symbols
|
|
781
|
+
charges: Atomic partial charges
|
|
782
|
+
cn: Coordination numbers
|
|
783
|
+
"""
|
|
784
|
+
n_atoms = len(coord)
|
|
785
|
+
|
|
786
|
+
for i in range(n_atoms):
|
|
787
|
+
t_xyz_4 = coord[i]
|
|
788
|
+
|
|
789
|
+
for j in range(i+1, n_atoms):
|
|
790
|
+
t_xyz_1 = coord[j]
|
|
791
|
+
|
|
792
|
+
for k in range(j+1, n_atoms):
|
|
793
|
+
t_xyz_2 = coord[k]
|
|
794
|
+
|
|
795
|
+
for l in range(k+1, n_atoms):
|
|
796
|
+
t_xyz_3 = coord[l]
|
|
797
|
+
|
|
798
|
+
# Calculate bond vectors
|
|
799
|
+
r_ij = coord[i] - coord[j]
|
|
800
|
+
r_ik = coord[i] - coord[k]
|
|
801
|
+
r_il = coord[i] - coord[l]
|
|
802
|
+
|
|
803
|
+
# Get bond parameters
|
|
804
|
+
covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
805
|
+
covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
806
|
+
covalent_length_il = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[l])
|
|
807
|
+
|
|
808
|
+
# Get element indices for parameter lookup
|
|
809
|
+
idx_i = self.select_idx(element_list[i])
|
|
810
|
+
idx_j = self.select_idx(element_list[j])
|
|
811
|
+
idx_k = self.select_idx(element_list[k])
|
|
812
|
+
idx_l = self.select_idx(element_list[l])
|
|
813
|
+
|
|
814
|
+
# Get Lindh parameters
|
|
815
|
+
d_ij_0 = self.dAv[idx_i][idx_j]
|
|
816
|
+
r_ij_0 = self.rAv[idx_i][idx_j]
|
|
817
|
+
alpha_ij = self.aAv[idx_i][idx_j]
|
|
818
|
+
|
|
819
|
+
d_ik_0 = self.dAv[idx_i][idx_k]
|
|
820
|
+
r_ik_0 = self.rAv[idx_i][idx_k]
|
|
821
|
+
alpha_ik = self.aAv[idx_i][idx_k]
|
|
822
|
+
|
|
823
|
+
d_il_0 = self.dAv[idx_i][idx_l]
|
|
824
|
+
r_il_0 = self.rAv[idx_i][idx_l]
|
|
825
|
+
alpha_il = self.aAv[idx_i][idx_l]
|
|
826
|
+
|
|
827
|
+
# Calculate squared distances
|
|
828
|
+
r_ij_2 = np.sum(r_ij**2)
|
|
829
|
+
r_ik_2 = np.sum(r_ik**2)
|
|
830
|
+
r_il_2 = np.sum(r_il**2)
|
|
831
|
+
|
|
832
|
+
# Calculate norms
|
|
833
|
+
norm_r_ij = np.sqrt(r_ij_2)
|
|
834
|
+
norm_r_ik = np.sqrt(r_ik_2)
|
|
835
|
+
norm_r_il = np.sqrt(r_il_2)
|
|
836
|
+
|
|
837
|
+
# Check for near-linear angles that would cause numerical issues
|
|
838
|
+
cosfi2 = np.dot(r_ij, r_ik) / (norm_r_ij * norm_r_ik)
|
|
839
|
+
if abs(abs(cosfi2) - 1.0) < 1.0e-1:
|
|
840
|
+
continue
|
|
841
|
+
|
|
842
|
+
cosfi3 = np.dot(r_ij, r_il) / (norm_r_ij * norm_r_il)
|
|
843
|
+
if abs(abs(cosfi3) - 1.0) < 1.0e-1:
|
|
844
|
+
continue
|
|
845
|
+
|
|
846
|
+
cosfi4 = np.dot(r_ik, r_il) / (norm_r_ik * norm_r_il)
|
|
847
|
+
if abs(abs(cosfi4) - 1.0) < 1.0e-1:
|
|
848
|
+
continue
|
|
849
|
+
|
|
850
|
+
# Get D4 parameters for each pair with charge scaling
|
|
851
|
+
c6_ij, c8_ij, r0_ij, q_ij = self.get_d4_parameters(
|
|
852
|
+
element_list[i], element_list[j], charges[i], charges[j])
|
|
853
|
+
c6_ik, c8_ik, r0_ik, q_ik = self.get_d4_parameters(
|
|
854
|
+
element_list[i], element_list[k], charges[i], charges[k])
|
|
855
|
+
c6_il, c8_il, r0_il, q_il = self.get_d4_parameters(
|
|
856
|
+
element_list[i], element_list[l], charges[i], charges[l])
|
|
857
|
+
|
|
858
|
+
# Disable direct D4 contributions to out-of-plane terms
|
|
859
|
+
kd = 0.0
|
|
860
|
+
|
|
861
|
+
# Calculate force constants for each bond
|
|
862
|
+
g_ij = self.calc_force_const(alpha_ij, covalent_length_ij, r_ij_2)
|
|
863
|
+
if norm_r_ij > 2.0 * covalent_length_ij:
|
|
864
|
+
g_ij += 0.5 * kd * self.calc_d4_force_const(
|
|
865
|
+
norm_r_ij, c6_ij, c8_ij, r0_ij, q_ij)
|
|
866
|
+
|
|
867
|
+
g_ik = self.calc_force_const(alpha_ik, covalent_length_ik, r_ik_2)
|
|
868
|
+
if norm_r_ik > 2.0 * covalent_length_ik:
|
|
869
|
+
g_ik += 0.5 * kd * self.calc_d4_force_const(
|
|
870
|
+
norm_r_ik, c6_ik, c8_ik, r0_ik, q_ik)
|
|
871
|
+
|
|
872
|
+
g_il = self.calc_force_const(alpha_il, covalent_length_il, r_il_2)
|
|
873
|
+
if norm_r_il > 2.0 * covalent_length_il:
|
|
874
|
+
g_il += 0.5 * kd * self.calc_d4_force_const(
|
|
875
|
+
norm_r_il, c6_il, c8_il, r0_il, q_il)
|
|
876
|
+
|
|
877
|
+
# Combined force constant for out-of-plane motion
|
|
878
|
+
t_ij = self.ko * g_ij * g_ik * g_il
|
|
879
|
+
|
|
880
|
+
# Apply special treatment for planar centers (D4 enhancement)
|
|
881
|
+
if 2.9 <= cn[i] <= 3.1: # Planar center (CN=3 typical for sp2)
|
|
882
|
+
t_ij *= 1.2 # Increase force constant for planar centers
|
|
883
|
+
|
|
884
|
+
# Calculate out-of-plane angle and derivatives
|
|
885
|
+
t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
|
|
886
|
+
theta, c = outofplane2(t_xyz)
|
|
887
|
+
|
|
888
|
+
# Extract derivatives
|
|
889
|
+
s_i = c[0]
|
|
890
|
+
s_j = c[1]
|
|
891
|
+
s_k = c[2]
|
|
892
|
+
s_l = c[3]
|
|
893
|
+
|
|
894
|
+
# Update off-diagonal blocks
|
|
895
|
+
for n in range(3):
|
|
896
|
+
for m in range(3):
|
|
897
|
+
self.cart_hess[i*3+n, j*3+m] += t_ij * s_i[n] * s_j[m]
|
|
898
|
+
self.cart_hess[i*3+n, k*3+m] += t_ij * s_i[n] * s_k[m]
|
|
899
|
+
self.cart_hess[i*3+n, l*3+m] += t_ij * s_i[n] * s_l[m]
|
|
900
|
+
self.cart_hess[j*3+n, k*3+m] += t_ij * s_j[n] * s_k[m]
|
|
901
|
+
self.cart_hess[j*3+n, l*3+m] += t_ij * s_j[n] * s_l[m]
|
|
902
|
+
self.cart_hess[k*3+n, l*3+m] += t_ij * s_k[n] * s_l[m]
|
|
903
|
+
|
|
904
|
+
# Update diagonal blocks (lower triangle)
|
|
905
|
+
for n in range(3):
|
|
906
|
+
for m in range(n):
|
|
907
|
+
self.cart_hess[i*3+n, i*3+m] += t_ij * s_i[n] * s_i[m]
|
|
908
|
+
self.cart_hess[j*3+n, j*3+m] += t_ij * s_j[n] * s_j[m]
|
|
909
|
+
self.cart_hess[k*3+n, k*3+m] += t_ij * s_k[n] * s_k[m]
|
|
910
|
+
self.cart_hess[l*3+n, l*3+m] += t_ij * s_l[n] * s_l[m]
|
|
911
|
+
|
|
912
|
+
def calculate_three_body_terms(self, coord, element_list, charges, cn):
|
|
913
|
+
"""
|
|
914
|
+
Calculate D4-specific three-body dispersion contributions.
|
|
915
|
+
|
|
916
|
+
Args:
|
|
917
|
+
coord: Atomic coordinates (Bohr)
|
|
918
|
+
element_list: List of element symbols
|
|
919
|
+
charges: Atomic partial charges
|
|
920
|
+
cn: Coordination numbers
|
|
921
|
+
"""
|
|
922
|
+
n_atoms = len(coord)
|
|
923
|
+
|
|
924
|
+
# Only apply three-body terms for larger systems to improve efficiency
|
|
925
|
+
if n_atoms < 3:
|
|
926
|
+
return
|
|
927
|
+
|
|
928
|
+
# Apply D4 three-body terms
|
|
929
|
+
# This is a simplified implementation for Hessian calculations
|
|
930
|
+
for i in range(n_atoms):
|
|
931
|
+
for j in range(i+1, n_atoms):
|
|
932
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
933
|
+
|
|
934
|
+
# Skip if atoms are too close (likely bonded)
|
|
935
|
+
cov_cutoff = (covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])) * 1.5
|
|
936
|
+
if r_ij < cov_cutoff:
|
|
937
|
+
continue
|
|
938
|
+
|
|
939
|
+
for k in range(j+1, n_atoms):
|
|
940
|
+
r_ik = np.linalg.norm(coord[i] - coord[k])
|
|
941
|
+
r_jk = np.linalg.norm(coord[j] - coord[k])
|
|
942
|
+
|
|
943
|
+
# Skip if atoms are too close (likely bonded)
|
|
944
|
+
if r_ik < (covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])) * 1.5:
|
|
945
|
+
continue
|
|
946
|
+
if r_jk < (covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])) * 1.5:
|
|
947
|
+
continue
|
|
948
|
+
|
|
949
|
+
# Get D4 parameters
|
|
950
|
+
c6_ij, c8_ij, r0_ij, q_ij = self.get_d4_parameters(
|
|
951
|
+
element_list[i], element_list[j], charges[i], charges[j])
|
|
952
|
+
c6_ik, c8_ik, r0_ik, q_ik = self.get_d4_parameters(
|
|
953
|
+
element_list[i], element_list[k], charges[i], charges[k])
|
|
954
|
+
c6_jk, c8_jk, r0_jk, q_jk = self.get_d4_parameters(
|
|
955
|
+
element_list[j], element_list[k], charges[j], charges[k])
|
|
956
|
+
|
|
957
|
+
# Calculate C9 coefficient for three-body term (Axilrod-Teller-Muto)
|
|
958
|
+
c9_ijk = np.sqrt(c6_ij * c6_ik * c6_jk)
|
|
959
|
+
|
|
960
|
+
# Apply charge scaling
|
|
961
|
+
q_scaling = np.exp(-self.d4params.gc * (charges[i]**2 + charges[j]**2 + charges[k]**2))
|
|
962
|
+
|
|
963
|
+
# Skip for very long-range interactions (r > 15 Bohr)
|
|
964
|
+
if r_ij > 15.0 or r_ik > 15.0 or r_jk > 15.0:
|
|
965
|
+
continue
|
|
966
|
+
|
|
967
|
+
# Calculate three-body term (simplified for Hessian implementation)
|
|
968
|
+
# Note: This is a very simplified approximation
|
|
969
|
+
threebody_scale = 0.002 * self.d4params.s9 * q_scaling * c9_ijk / (r_ij * r_ik * r_jk)**3
|
|
970
|
+
|
|
971
|
+
# Add minimal contribution to Hessian - just main diagonal elements
|
|
972
|
+
# For proper implementation, full derivatives would be needed
|
|
973
|
+
for idx in [i, j, k]:
|
|
974
|
+
for n in range(3):
|
|
975
|
+
self.cart_hess[idx*3+n, idx*3+n] += threebody_scale
|
|
976
|
+
|
|
977
|
+
def main(self, coord, element_list, cart_gradient):
|
|
978
|
+
"""
|
|
979
|
+
Calculate approximate Hessian using Lindh's 2007 model with D4 dispersion.
|
|
980
|
+
|
|
981
|
+
Args:
|
|
982
|
+
coord: Atomic coordinates (Bohr)
|
|
983
|
+
element_list: List of element symbols
|
|
984
|
+
cart_gradient: Cartesian gradient vector
|
|
985
|
+
|
|
986
|
+
Returns:
|
|
987
|
+
hess_proj: Projected approximate Hessian matrix
|
|
988
|
+
"""
|
|
989
|
+
print("Generating Lindh's (2007) approximate Hessian with D4 dispersion...")
|
|
990
|
+
|
|
991
|
+
# Scale eigenvalues based on gradient norm (smaller scale for larger gradients)
|
|
992
|
+
norm_grad = np.linalg.norm(cart_gradient)
|
|
993
|
+
scale = 0.1
|
|
994
|
+
eigval_scale = scale * np.exp(-1 * norm_grad**2.0)
|
|
995
|
+
|
|
996
|
+
# Initialize Hessian matrix
|
|
997
|
+
n_atoms = len(coord)
|
|
998
|
+
self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
|
|
999
|
+
|
|
1000
|
+
# Calculate atomic charges for D4 scaling
|
|
1001
|
+
charges = self.estimate_atomic_charges(coord, element_list)
|
|
1002
|
+
|
|
1003
|
+
# Calculate coordination numbers for D4 scaling
|
|
1004
|
+
cn = self.calculate_coordination_numbers(coord, element_list)
|
|
1005
|
+
|
|
1006
|
+
# Calculate individual contributions
|
|
1007
|
+
self.lindh2007_bond(coord, element_list, charges, cn)
|
|
1008
|
+
self.lindh2007_angle(coord, element_list, charges, cn)
|
|
1009
|
+
self.lindh2007_dihedral_angle(coord, element_list, charges, cn)
|
|
1010
|
+
self.lindh2007_out_of_plane(coord, element_list, charges, cn)
|
|
1011
|
+
|
|
1012
|
+
# Add D4-specific three-body terms
|
|
1013
|
+
self.calculate_three_body_terms(coord, element_list, charges, cn)
|
|
1014
|
+
|
|
1015
|
+
# Symmetrize the Hessian matrix
|
|
1016
|
+
for i in range(n_atoms*3):
|
|
1017
|
+
for j in range(i):
|
|
1018
|
+
if abs(self.cart_hess[i, j]) < 1.0e-10:
|
|
1019
|
+
self.cart_hess[i, j] = self.cart_hess[j, i]
|
|
1020
|
+
else:
|
|
1021
|
+
self.cart_hess[j, i] = self.cart_hess[i, j]
|
|
1022
|
+
|
|
1023
|
+
# Project out translational and rotational degrees of freedom
|
|
1024
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
|
|
1025
|
+
|
|
1026
|
+
# Adjust eigenvalues for stability based on gradient magnitude
|
|
1027
|
+
eigenvalues, eigenvectors = np.linalg.eigh(hess_proj)
|
|
1028
|
+
hess_proj = np.dot(np.dot(eigenvectors, np.diag(np.abs(eigenvalues) * eigval_scale)), np.transpose(eigenvectors))
|
|
1029
|
+
|
|
1030
|
+
return hess_proj
|