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,559 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import itertools
|
|
3
|
+
|
|
4
|
+
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib, UFF_VDW_distance_lib, D4Parameters, triple_covalent_radii_lib
|
|
5
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
6
|
+
from multioptpy.Parameters.parameter import UFF_VDW_distance_lib, number_element
|
|
7
|
+
from multioptpy.Coordinate.redundant_coordinate import RedundantInternalCoordinates
|
|
8
|
+
from multioptpy.Utils.bond_connectivity import BondConnectivity
|
|
9
|
+
|
|
10
|
+
class SchlegelD4ApproxHessian:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
"""
|
|
13
|
+
Schlegel's approximate Hessian with D4 dispersion corrections and special handling for cyano groups
|
|
14
|
+
References:
|
|
15
|
+
- Schlegel: Journal of Molecular Structure: THEOCHEM Volumes 398–399, 30 June 1997, Pages 55-61
|
|
16
|
+
- D4: E. Caldeweyher, C. Bannwarth, S. Grimme, J. Chem. Phys., 2017, 147, 034112
|
|
17
|
+
"""
|
|
18
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
19
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
20
|
+
|
|
21
|
+
# D4 dispersion parameters
|
|
22
|
+
self.d4_params = D4Parameters()
|
|
23
|
+
|
|
24
|
+
# Cyano group parameters - enhanced force constants
|
|
25
|
+
self.cn_stretch_factor = 2.0 # Enhance stretch force constants for C≡N triple bond
|
|
26
|
+
self.cn_angle_factor = 1.5 # Enhance angle force constants involving C≡N
|
|
27
|
+
self.cn_torsion_factor = 0.5 # Reduce torsion force constants involving C≡N (more flexible)
|
|
28
|
+
|
|
29
|
+
def detect_cyano_groups(self, coord, element_list):
|
|
30
|
+
"""Detect C≡N triple bonds in the structure"""
|
|
31
|
+
cyano_atoms = [] # List of (C_idx, N_idx) tuples
|
|
32
|
+
|
|
33
|
+
for i in range(len(coord)):
|
|
34
|
+
if element_list[i] != 'C':
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
for j in range(len(coord)):
|
|
38
|
+
if i == j or element_list[j] != 'N':
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
# Calculate distance between C and N
|
|
42
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
43
|
+
|
|
44
|
+
# Check if distance is close to a triple bond length
|
|
45
|
+
cn_triple_bond = triple_covalent_radii_lib('C') + triple_covalent_radii_lib('N')
|
|
46
|
+
|
|
47
|
+
if abs(r_ij - cn_triple_bond) < 0.3: # Within 0.3 bohr of ideal length
|
|
48
|
+
# Check if C is connected to only one other atom (besides N)
|
|
49
|
+
connections_to_c = 0
|
|
50
|
+
for k in range(len(coord)):
|
|
51
|
+
if k == i or k == j:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
r_ik = np.linalg.norm(coord[i] - coord[k])
|
|
55
|
+
cov_dist = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
56
|
+
|
|
57
|
+
if r_ik < 1.3 * cov_dist: # Using 1.3 as a factor to account for bond length variations
|
|
58
|
+
connections_to_c += 1
|
|
59
|
+
|
|
60
|
+
# If C has only one other connection, it's likely a terminal cyano group
|
|
61
|
+
if connections_to_c <= 1:
|
|
62
|
+
cyano_atoms.append((i, j))
|
|
63
|
+
|
|
64
|
+
return cyano_atoms
|
|
65
|
+
|
|
66
|
+
def calculate_coordination_numbers(self, coord, element_list):
|
|
67
|
+
"""Calculate atomic coordination numbers for D4 scaling"""
|
|
68
|
+
n_atoms = len(coord)
|
|
69
|
+
cn = np.zeros(n_atoms)
|
|
70
|
+
|
|
71
|
+
for i in range(n_atoms):
|
|
72
|
+
for j in range(n_atoms):
|
|
73
|
+
if i == j:
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
# Calculate distance
|
|
77
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
78
|
+
|
|
79
|
+
# Get covalent radii
|
|
80
|
+
r_cov_i = covalent_radii_lib(element_list[i])
|
|
81
|
+
r_cov_j = covalent_radii_lib(element_list[j])
|
|
82
|
+
|
|
83
|
+
# Coordination number contribution using counting function
|
|
84
|
+
# k1 = 16.0, k2 = 4.0/3.0 (standard values from DFT-D4)
|
|
85
|
+
k1 = 16.0
|
|
86
|
+
k2 = 4.0/3.0
|
|
87
|
+
r0 = r_cov_i + r_cov_j
|
|
88
|
+
|
|
89
|
+
# Avoid overflow in exp
|
|
90
|
+
if k1 * (r_ij / r0 - 1.0) > 25.0:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
cn_contrib = 1.0 / (1.0 + np.exp(-k1 * (k2 * r0 / r_ij - 1.0)))
|
|
94
|
+
cn[i] += cn_contrib
|
|
95
|
+
|
|
96
|
+
return cn
|
|
97
|
+
|
|
98
|
+
def estimate_atomic_charges(self, coord, element_list):
|
|
99
|
+
"""
|
|
100
|
+
Estimate atomic charges using electronegativity equalization
|
|
101
|
+
Simplified version for Hessian generation
|
|
102
|
+
"""
|
|
103
|
+
n_atoms = len(coord)
|
|
104
|
+
charges = np.zeros(n_atoms)
|
|
105
|
+
|
|
106
|
+
# Calculate reference electronegativities
|
|
107
|
+
en_list = [self.d4_params.get_electronegativity(elem) for elem in element_list]
|
|
108
|
+
|
|
109
|
+
# Simple charge estimation based on electronegativity differences
|
|
110
|
+
# This is a very simplified model
|
|
111
|
+
for i in range(n_atoms):
|
|
112
|
+
for j in range(i+1, n_atoms):
|
|
113
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
114
|
+
r_cov_sum = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
115
|
+
|
|
116
|
+
# Only consider bonded atoms
|
|
117
|
+
if r_ij < 1.3 * r_cov_sum:
|
|
118
|
+
en_diff = en_list[j] - en_list[i]
|
|
119
|
+
charge_transfer = 0.1 * en_diff # Simple approximation
|
|
120
|
+
charges[i] += charge_transfer
|
|
121
|
+
charges[j] -= charge_transfer
|
|
122
|
+
|
|
123
|
+
return charges
|
|
124
|
+
|
|
125
|
+
def return_schlegel_const(self, element_1, element_2):
|
|
126
|
+
"""Return Schlegel's constant for a given element pair"""
|
|
127
|
+
if type(element_1) is int:
|
|
128
|
+
element_1 = number_element(element_1)
|
|
129
|
+
if type(element_2) is int:
|
|
130
|
+
element_2 = number_element(element_2)
|
|
131
|
+
|
|
132
|
+
parameter_B_matrix = [
|
|
133
|
+
[0.2573, 0.3401, 0.6937, 0.7126, 0.8335, 0.9491, 0.9491],
|
|
134
|
+
[0.3401, 0.9652, 1.2843, 1.4725, 1.6549, 1.7190, 1.7190],
|
|
135
|
+
[0.6937, 1.2843, 1.6925, 1.8238, 2.1164, 2.3185, 2.3185],
|
|
136
|
+
[0.7126, 1.4725, 1.8238, 2.0203, 2.2137, 2.5206, 2.5206],
|
|
137
|
+
[0.8335, 1.6549, 2.1164, 2.2137, 2.3718, 2.5110, 2.5110],
|
|
138
|
+
[0.9491, 1.7190, 2.3185, 2.5206, 2.5110, 2.5110, 2.5110],
|
|
139
|
+
[0.9491, 1.7190, 2.3185, 2.5206, 2.5110, 2.5110, 2.5110]
|
|
140
|
+
] # Bohr
|
|
141
|
+
|
|
142
|
+
first_period_table = ["H", "He"]
|
|
143
|
+
second_period_table = ["Li", "Be", "B", "C", "N", "O", "F", "Ne"]
|
|
144
|
+
third_period_table = ["Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar"]
|
|
145
|
+
fourth_period_table = ["K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br","Kr"]
|
|
146
|
+
fifth_period_table = ["Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc","Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te","I", "Xe"]
|
|
147
|
+
sixth_period_table = ["Cs", "Ba", "La","Ce","Pr","Nd","Pm","Sm", "Eu", "Gd", "Tb", "Dy" ,"Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn"]
|
|
148
|
+
|
|
149
|
+
if element_1 in first_period_table:
|
|
150
|
+
idx_1 = 0
|
|
151
|
+
elif element_1 in second_period_table:
|
|
152
|
+
idx_1 = 1
|
|
153
|
+
elif element_1 in third_period_table:
|
|
154
|
+
idx_1 = 2
|
|
155
|
+
elif element_1 in fourth_period_table:
|
|
156
|
+
idx_1 = 3
|
|
157
|
+
elif element_1 in fifth_period_table:
|
|
158
|
+
idx_1 = 4
|
|
159
|
+
elif element_1 in sixth_period_table:
|
|
160
|
+
idx_1 = 5
|
|
161
|
+
else:
|
|
162
|
+
idx_1 = 6
|
|
163
|
+
|
|
164
|
+
if element_2 in first_period_table:
|
|
165
|
+
idx_2 = 0
|
|
166
|
+
elif element_2 in second_period_table:
|
|
167
|
+
idx_2 = 1
|
|
168
|
+
elif element_2 in third_period_table:
|
|
169
|
+
idx_2 = 2
|
|
170
|
+
elif element_2 in fourth_period_table:
|
|
171
|
+
idx_2 = 3
|
|
172
|
+
elif element_2 in fifth_period_table:
|
|
173
|
+
idx_2 = 4
|
|
174
|
+
elif element_2 in sixth_period_table:
|
|
175
|
+
idx_2 = 5
|
|
176
|
+
else:
|
|
177
|
+
idx_2 = 6
|
|
178
|
+
|
|
179
|
+
const_b = parameter_B_matrix[idx_1][idx_2]
|
|
180
|
+
return const_b
|
|
181
|
+
|
|
182
|
+
def d4_damping_function(self, r_ij, r0, order=6):
|
|
183
|
+
"""D4 rational damping function"""
|
|
184
|
+
a1 = self.d4_params.a1
|
|
185
|
+
a2 = self.d4_params.a2
|
|
186
|
+
|
|
187
|
+
if order == 6:
|
|
188
|
+
return 1.0 / (1.0 + 6.0 * (r_ij / (a1 * r0)) ** a2)
|
|
189
|
+
elif order == 8:
|
|
190
|
+
return 1.0 / (1.0 + 6.0 * (r_ij / (a2 * r0)) ** a1)
|
|
191
|
+
return 0.0
|
|
192
|
+
|
|
193
|
+
def charge_scale_factor(self, charge, element):
|
|
194
|
+
"""Calculate charge scaling factor for D4"""
|
|
195
|
+
ga = self.d4_params.ga # D4 charge scaling parameter (default=3.0)
|
|
196
|
+
q_ref = self.d4_params.get_electronegativity(element)
|
|
197
|
+
|
|
198
|
+
# Prevent numerical issues with large exponents
|
|
199
|
+
exp_arg = -ga * abs(charge)
|
|
200
|
+
if exp_arg < -50.0: # Avoid underflow
|
|
201
|
+
return 0.0
|
|
202
|
+
|
|
203
|
+
return np.exp(exp_arg)
|
|
204
|
+
|
|
205
|
+
def get_d4_parameters(self, elem1, elem2, q1=0.0, q2=0.0, cn1=None, cn2=None):
|
|
206
|
+
"""Get D4 parameters for a pair of elements with charge scaling"""
|
|
207
|
+
# Get polarizabilities
|
|
208
|
+
alpha1 = self.d4_params.get_polarizability(elem1)
|
|
209
|
+
alpha2 = self.d4_params.get_polarizability(elem2)
|
|
210
|
+
|
|
211
|
+
# Charge scaling
|
|
212
|
+
qscale1 = self.charge_scale_factor(q1, elem1)
|
|
213
|
+
qscale2 = self.charge_scale_factor(q2, elem2)
|
|
214
|
+
|
|
215
|
+
# Get R4/R2 values
|
|
216
|
+
r4r2_1 = self.d4_params.get_r4r2(elem1)
|
|
217
|
+
r4r2_2 = self.d4_params.get_r4r2(elem2)
|
|
218
|
+
|
|
219
|
+
# C6 coefficients with charge scaling
|
|
220
|
+
c6_1 = alpha1 * r4r2_1 * qscale1
|
|
221
|
+
c6_2 = alpha2 * r4r2_2 * qscale2
|
|
222
|
+
c6_param = 2.0 * c6_1 * c6_2 / (c6_1 + c6_2) # Effective C6 using harmonic mean
|
|
223
|
+
|
|
224
|
+
# C8 coefficients
|
|
225
|
+
c8_param = 3.0 * c6_param * np.sqrt(r4r2_1 * r4r2_2)
|
|
226
|
+
|
|
227
|
+
# r0 parameter (combined vdW radii)
|
|
228
|
+
r0_param = np.sqrt(UFF_VDW_distance_lib(elem1) * UFF_VDW_distance_lib(elem2))
|
|
229
|
+
|
|
230
|
+
return c6_param, c8_param, r0_param
|
|
231
|
+
|
|
232
|
+
def calc_d4_correction(self, r_ij, elem1, elem2, q1=0.0, q2=0.0, cn1=None, cn2=None):
|
|
233
|
+
"""Calculate D4 dispersion correction to the force constant"""
|
|
234
|
+
# Get D4 parameters with charge scaling
|
|
235
|
+
c6_param, c8_param, r0_param = self.get_d4_parameters(
|
|
236
|
+
elem1, elem2, q1=q1, q2=q2, cn1=cn1, cn2=cn2
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
# Damping functions
|
|
240
|
+
damp6 = self.d4_damping_function(r_ij, r0_param, order=6)
|
|
241
|
+
damp8 = self.d4_damping_function(r_ij, r0_param, order=8)
|
|
242
|
+
|
|
243
|
+
# D4 energy contribution
|
|
244
|
+
s6 = self.d4_params.s6
|
|
245
|
+
s8 = self.d4_params.s8
|
|
246
|
+
e_disp = -s6 * c6_param / r_ij**6 * damp6 - s8 * c8_param / r_ij**8 * damp8
|
|
247
|
+
|
|
248
|
+
# Approximate second derivative (force constant)
|
|
249
|
+
fc_disp = s6 * c6_param * (42.0 / r_ij**8) * damp6 + s8 * c8_param * (72.0 / r_ij**10) * damp8
|
|
250
|
+
|
|
251
|
+
return fc_disp * 0.01 # Scale factor to match overall Hessian scale
|
|
252
|
+
|
|
253
|
+
def calculate_three_body_term(self, coord, element_list, charges, cn):
|
|
254
|
+
"""Calculate three-body dispersion contribution"""
|
|
255
|
+
n_atoms = len(coord)
|
|
256
|
+
s9 = self.d4_params.s9
|
|
257
|
+
|
|
258
|
+
# Skip if three-body term is turned off
|
|
259
|
+
if abs(s9) < 1e-12:
|
|
260
|
+
return np.zeros((3 * n_atoms, 3 * n_atoms))
|
|
261
|
+
|
|
262
|
+
# Initialize three-body Hessian contribution
|
|
263
|
+
three_body_hess = np.zeros((3 * n_atoms, 3 * n_atoms))
|
|
264
|
+
|
|
265
|
+
# Loop over all atom triplets
|
|
266
|
+
for i in range(n_atoms):
|
|
267
|
+
for j in range(i+1, n_atoms):
|
|
268
|
+
for k in range(j+1, n_atoms):
|
|
269
|
+
# Get positions
|
|
270
|
+
r_i = coord[i]
|
|
271
|
+
r_j = coord[j]
|
|
272
|
+
r_k = coord[k]
|
|
273
|
+
|
|
274
|
+
# Calculate interatomic distances
|
|
275
|
+
r_ij = np.linalg.norm(r_i - r_j)
|
|
276
|
+
r_jk = np.linalg.norm(r_j - r_k)
|
|
277
|
+
r_ki = np.linalg.norm(r_k - r_i)
|
|
278
|
+
|
|
279
|
+
# Get coordination-number scaled C6 coefficients
|
|
280
|
+
c6_ij, _, r0_ij = self.get_d4_parameters(
|
|
281
|
+
element_list[i], element_list[j],
|
|
282
|
+
q1=charges[i], q2=charges[j],
|
|
283
|
+
cn1=cn[i], cn2=cn[j]
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
c6_jk, _, r0_jk = self.get_d4_parameters(
|
|
287
|
+
element_list[j], element_list[k],
|
|
288
|
+
q1=charges[j], q2=charges[k],
|
|
289
|
+
cn1=cn[j], cn2=cn[k]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
c6_ki, _, r0_ki = self.get_d4_parameters(
|
|
293
|
+
element_list[k], element_list[i],
|
|
294
|
+
q1=charges[k], q2=charges[i],
|
|
295
|
+
cn1=cn[k], cn2=cn[i]
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# Calculate geometric mean of C6 coefficients
|
|
299
|
+
c9 = np.cbrt(c6_ij * c6_jk * c6_ki)
|
|
300
|
+
|
|
301
|
+
# Calculate damping
|
|
302
|
+
damp_ij = self.d4_damping_function(r_ij, r0_ij)
|
|
303
|
+
damp_jk = self.d4_damping_function(r_jk, r0_jk)
|
|
304
|
+
damp_ki = self.d4_damping_function(r_ki, r0_ki)
|
|
305
|
+
damp = damp_ij * damp_jk * damp_ki
|
|
306
|
+
|
|
307
|
+
# Skip if damping is too small
|
|
308
|
+
if damp < 1e-8:
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
# Calculate angle factor
|
|
312
|
+
r_ij_vec = r_j - r_i
|
|
313
|
+
r_jk_vec = r_k - r_j
|
|
314
|
+
r_ki_vec = r_i - r_k
|
|
315
|
+
|
|
316
|
+
cos_ijk = np.dot(r_ij_vec, r_jk_vec) / (r_ij * r_jk)
|
|
317
|
+
cos_jki = np.dot(r_jk_vec, -r_ki_vec) / (r_jk * r_ki)
|
|
318
|
+
cos_kij = np.dot(-r_ki_vec, r_ij_vec) / (r_ki * r_ij)
|
|
319
|
+
|
|
320
|
+
angle_factor = 1.0 + 3.0 * cos_ijk * cos_jki * cos_kij
|
|
321
|
+
|
|
322
|
+
# Calculate three-body energy term
|
|
323
|
+
e_3 = -s9 * angle_factor * c9 * damp / (r_ij * r_jk * r_ki) ** 3
|
|
324
|
+
|
|
325
|
+
# Approximate Hessian contribution (simplified)
|
|
326
|
+
# We use a small scaling factor to avoid dominating the Hessian
|
|
327
|
+
fc_scale = 0.002 * s9 * angle_factor * c9 * damp
|
|
328
|
+
|
|
329
|
+
# Add approximate three-body contributions to Hessian
|
|
330
|
+
for n in range(3):
|
|
331
|
+
for m in range(3):
|
|
332
|
+
# Properly define all indices
|
|
333
|
+
idx_i_n = i * 3 + n
|
|
334
|
+
idx_j_n = j * 3 + n
|
|
335
|
+
idx_k_n = k * 3 + n
|
|
336
|
+
|
|
337
|
+
idx_i_m = i * 3 + m
|
|
338
|
+
idx_j_m = j * 3 + m
|
|
339
|
+
idx_k_m = k * 3 + m
|
|
340
|
+
|
|
341
|
+
# Diagonal blocks (diagonal atoms)
|
|
342
|
+
if n == m:
|
|
343
|
+
three_body_hess[idx_i_n, idx_i_n] += fc_scale / (r_ij**6) + fc_scale / (r_ki**6)
|
|
344
|
+
three_body_hess[idx_j_n, idx_j_n] += fc_scale / (r_ij**6) + fc_scale / (r_jk**6)
|
|
345
|
+
three_body_hess[idx_k_n, idx_k_n] += fc_scale / (r_jk**6) + fc_scale / (r_ki**6)
|
|
346
|
+
|
|
347
|
+
# Off-diagonal blocks (between atoms)
|
|
348
|
+
three_body_hess[idx_i_n, idx_j_m] -= fc_scale / (r_ij**6)
|
|
349
|
+
three_body_hess[idx_j_m, idx_i_n] -= fc_scale / (r_ij**6)
|
|
350
|
+
|
|
351
|
+
three_body_hess[idx_j_n, idx_k_m] -= fc_scale / (r_jk**6)
|
|
352
|
+
three_body_hess[idx_k_m, idx_j_n] -= fc_scale / (r_jk**6)
|
|
353
|
+
|
|
354
|
+
three_body_hess[idx_k_n, idx_i_m] -= fc_scale / (r_ki**6)
|
|
355
|
+
three_body_hess[idx_i_m, idx_k_n] -= fc_scale / (r_ki**6)
|
|
356
|
+
|
|
357
|
+
return three_body_hess
|
|
358
|
+
|
|
359
|
+
def guess_schlegel_hessian(self, coord, element_list, charges, cn):
|
|
360
|
+
"""
|
|
361
|
+
Calculate approximate Hessian using Schlegel's approach augmented with D4 dispersion
|
|
362
|
+
and special handling for cyano groups
|
|
363
|
+
"""
|
|
364
|
+
# Detect cyano groups
|
|
365
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
366
|
+
cyano_set = set()
|
|
367
|
+
for c_idx, n_idx in cyano_atoms:
|
|
368
|
+
cyano_set.add(c_idx)
|
|
369
|
+
cyano_set.add(n_idx)
|
|
370
|
+
|
|
371
|
+
# Setup connectivity tables using BondConnectivity utility
|
|
372
|
+
BC = BondConnectivity()
|
|
373
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
374
|
+
connectivity_table = [BC.bond_connect_table(b_c_mat),
|
|
375
|
+
BC.angle_connect_table(b_c_mat),
|
|
376
|
+
BC.dihedral_angle_connect_table(b_c_mat)]
|
|
377
|
+
|
|
378
|
+
# Initialize RIC index list for all atom pairs
|
|
379
|
+
RIC_idx_list = [[i[0], i[1]] for i in itertools.combinations(range(len(coord)), 2)]
|
|
380
|
+
self.RIC_variable_num = len(RIC_idx_list)
|
|
381
|
+
RIC_approx_diag_hessian = [0.0] * self.RIC_variable_num
|
|
382
|
+
|
|
383
|
+
# Process connectivity table to build Hessian
|
|
384
|
+
for idx_list in connectivity_table:
|
|
385
|
+
for idx in idx_list:
|
|
386
|
+
# Bond stretching terms
|
|
387
|
+
if len(idx) == 2:
|
|
388
|
+
tmp_idx = sorted([idx[0], idx[1]])
|
|
389
|
+
distance = np.linalg.norm(coord[idx[0]] - coord[idx[1]])
|
|
390
|
+
|
|
391
|
+
elem_1 = element_list[idx[0]]
|
|
392
|
+
elem_2 = element_list[idx[1]]
|
|
393
|
+
const_b = self.return_schlegel_const(elem_1, elem_2)
|
|
394
|
+
tmpnum = RIC_idx_list.index(tmp_idx)
|
|
395
|
+
|
|
396
|
+
# Base Schlegel force constant
|
|
397
|
+
F = 1.734 / (distance - const_b) ** 3
|
|
398
|
+
|
|
399
|
+
# Check if this is a cyano bond
|
|
400
|
+
is_cyano_bond = False
|
|
401
|
+
for c_idx, n_idx in cyano_atoms:
|
|
402
|
+
if (idx[0] == c_idx and idx[1] == n_idx) or (idx[0] == n_idx and idx[1] == c_idx):
|
|
403
|
+
is_cyano_bond = True
|
|
404
|
+
break
|
|
405
|
+
|
|
406
|
+
# Add D4 dispersion contribution with charge scaling
|
|
407
|
+
d4_correction = self.calc_d4_correction(
|
|
408
|
+
distance, elem_1, elem_2,
|
|
409
|
+
q1=charges[idx[0]], q2=charges[idx[1]],
|
|
410
|
+
cn1=cn[idx[0]], cn2=cn[idx[1]]
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
if is_cyano_bond:
|
|
414
|
+
# Enhanced force constant for C≡N triple bond
|
|
415
|
+
RIC_approx_diag_hessian[tmpnum] += self.cn_stretch_factor * F + d4_correction
|
|
416
|
+
else:
|
|
417
|
+
RIC_approx_diag_hessian[tmpnum] += F + d4_correction
|
|
418
|
+
|
|
419
|
+
# Angle bending terms
|
|
420
|
+
elif len(idx) == 3:
|
|
421
|
+
tmp_idx_1 = sorted([idx[0], idx[1]])
|
|
422
|
+
tmp_idx_2 = sorted([idx[1], idx[2]])
|
|
423
|
+
elem_1 = element_list[idx[0]]
|
|
424
|
+
elem_2 = element_list[idx[1]]
|
|
425
|
+
elem_3 = element_list[idx[2]]
|
|
426
|
+
tmpnum_1 = RIC_idx_list.index(tmp_idx_1)
|
|
427
|
+
tmpnum_2 = RIC_idx_list.index(tmp_idx_2)
|
|
428
|
+
|
|
429
|
+
# Check if angle involves cyano group
|
|
430
|
+
is_cyano_angle = (idx[0] in cyano_set or idx[1] in cyano_set or idx[2] in cyano_set)
|
|
431
|
+
|
|
432
|
+
# Base Schlegel force constant
|
|
433
|
+
if elem_1 == "H" or elem_3 == "H":
|
|
434
|
+
F_angle = 0.160
|
|
435
|
+
else:
|
|
436
|
+
F_angle = 0.250
|
|
437
|
+
|
|
438
|
+
# Add D4 dispersion contribution with charge scaling
|
|
439
|
+
d3_r1 = np.linalg.norm(coord[idx[0]] - coord[idx[1]])
|
|
440
|
+
d3_r2 = np.linalg.norm(coord[idx[1]] - coord[idx[2]])
|
|
441
|
+
|
|
442
|
+
d4_correction_1 = self.calc_d4_correction(
|
|
443
|
+
d3_r1, elem_1, elem_2,
|
|
444
|
+
q1=charges[idx[0]], q2=charges[idx[1]],
|
|
445
|
+
cn1=cn[idx[0]], cn2=cn[idx[1]]
|
|
446
|
+
) * 0.2
|
|
447
|
+
|
|
448
|
+
d4_correction_2 = self.calc_d4_correction(
|
|
449
|
+
d3_r2, elem_2, elem_3,
|
|
450
|
+
q1=charges[idx[1]], q2=charges[idx[2]],
|
|
451
|
+
cn1=cn[idx[1]], cn2=cn[idx[2]]
|
|
452
|
+
) * 0.2
|
|
453
|
+
|
|
454
|
+
if is_cyano_angle:
|
|
455
|
+
# Enhanced angle force constants for angles involving C≡N
|
|
456
|
+
RIC_approx_diag_hessian[tmpnum_1] += self.cn_angle_factor * F_angle + d4_correction_1
|
|
457
|
+
RIC_approx_diag_hessian[tmpnum_2] += self.cn_angle_factor * F_angle + d4_correction_2
|
|
458
|
+
else:
|
|
459
|
+
RIC_approx_diag_hessian[tmpnum_1] += F_angle + d4_correction_1
|
|
460
|
+
RIC_approx_diag_hessian[tmpnum_2] += F_angle + d4_correction_2
|
|
461
|
+
|
|
462
|
+
# Torsion (dihedral) terms
|
|
463
|
+
elif len(idx) == 4:
|
|
464
|
+
tmp_idx_1 = sorted([idx[0], idx[1]])
|
|
465
|
+
tmp_idx_2 = sorted([idx[1], idx[2]])
|
|
466
|
+
tmp_idx_3 = sorted([idx[2], idx[3]])
|
|
467
|
+
elem_1 = element_list[idx[0]]
|
|
468
|
+
elem_2 = element_list[idx[1]]
|
|
469
|
+
elem_3 = element_list[idx[2]]
|
|
470
|
+
elem_4 = element_list[idx[3]]
|
|
471
|
+
distance = np.linalg.norm(coord[idx[1]] - coord[idx[2]])
|
|
472
|
+
bond_length = covalent_radii_lib(elem_2) + covalent_radii_lib(elem_3)
|
|
473
|
+
tmpnum_1 = RIC_idx_list.index(tmp_idx_1)
|
|
474
|
+
tmpnum_2 = RIC_idx_list.index(tmp_idx_2)
|
|
475
|
+
tmpnum_3 = RIC_idx_list.index(tmp_idx_3)
|
|
476
|
+
|
|
477
|
+
# Base Schlegel torsion force constant
|
|
478
|
+
F_torsion = 0.0023 - 0.07 * (distance - bond_length)
|
|
479
|
+
|
|
480
|
+
# Check if torsion involves cyano group
|
|
481
|
+
is_cyano_torsion = (idx[0] in cyano_set or idx[1] in cyano_set or
|
|
482
|
+
idx[2] in cyano_set or idx[3] in cyano_set)
|
|
483
|
+
|
|
484
|
+
# Add D4 dispersion contribution with charge scaling
|
|
485
|
+
d3_r1 = np.linalg.norm(coord[idx[0]] - coord[idx[1]])
|
|
486
|
+
d3_r2 = np.linalg.norm(coord[idx[1]] - coord[idx[2]])
|
|
487
|
+
d3_r3 = np.linalg.norm(coord[idx[2]] - coord[idx[3]])
|
|
488
|
+
|
|
489
|
+
d4_correction_1 = self.calc_d4_correction(
|
|
490
|
+
d3_r1, elem_1, elem_2,
|
|
491
|
+
q1=charges[idx[0]], q2=charges[idx[1]],
|
|
492
|
+
cn1=cn[idx[0]], cn2=cn[idx[1]]
|
|
493
|
+
) * 0.05
|
|
494
|
+
|
|
495
|
+
d4_correction_2 = self.calc_d4_correction(
|
|
496
|
+
d3_r2, elem_2, elem_3,
|
|
497
|
+
q1=charges[idx[1]], q2=charges[idx[2]],
|
|
498
|
+
cn1=cn[idx[1]], cn2=cn[idx[2]]
|
|
499
|
+
) * 0.05
|
|
500
|
+
|
|
501
|
+
d4_correction_3 = self.calc_d4_correction(
|
|
502
|
+
d3_r3, elem_3, elem_4,
|
|
503
|
+
q1=charges[idx[2]], q2=charges[idx[3]],
|
|
504
|
+
cn1=cn[idx[2]], cn2=cn[idx[3]]
|
|
505
|
+
) * 0.05
|
|
506
|
+
|
|
507
|
+
if is_cyano_torsion:
|
|
508
|
+
# Reduced torsion force constants for torsions involving C≡N
|
|
509
|
+
RIC_approx_diag_hessian[tmpnum_1] += self.cn_torsion_factor * F_torsion + d4_correction_1
|
|
510
|
+
RIC_approx_diag_hessian[tmpnum_2] += self.cn_torsion_factor * F_torsion + d4_correction_2
|
|
511
|
+
RIC_approx_diag_hessian[tmpnum_3] += self.cn_torsion_factor * F_torsion + d4_correction_3
|
|
512
|
+
else:
|
|
513
|
+
RIC_approx_diag_hessian[tmpnum_1] += F_torsion + d4_correction_1
|
|
514
|
+
RIC_approx_diag_hessian[tmpnum_2] += F_torsion + d4_correction_2
|
|
515
|
+
RIC_approx_diag_hessian[tmpnum_3] += F_torsion + d4_correction_3
|
|
516
|
+
|
|
517
|
+
# Convert to numpy array
|
|
518
|
+
RIC_approx_hessian = np.diag(RIC_approx_diag_hessian).astype("float64")
|
|
519
|
+
return RIC_approx_hessian
|
|
520
|
+
|
|
521
|
+
def main(self, coord, element_list, cart_gradient):
|
|
522
|
+
"""Main method to calculate the approximate Hessian"""
|
|
523
|
+
print("Generating Schlegel's approximate Hessian with D4 dispersion correction...")
|
|
524
|
+
|
|
525
|
+
# Calculate coordination numbers and atomic charges for D4
|
|
526
|
+
cn = self.calculate_coordination_numbers(coord, element_list)
|
|
527
|
+
charges = self.estimate_atomic_charges(coord, element_list)
|
|
528
|
+
|
|
529
|
+
# Calculate B matrix for redundant internal coordinates
|
|
530
|
+
b_mat = RedundantInternalCoordinates().B_matrix(coord)
|
|
531
|
+
self.RIC_variable_num = len(b_mat)
|
|
532
|
+
|
|
533
|
+
# Calculate approximate Hessian in internal coordinates
|
|
534
|
+
int_approx_hess = self.guess_schlegel_hessian(coord, element_list, charges, cn)
|
|
535
|
+
|
|
536
|
+
# Convert to Cartesian coordinates
|
|
537
|
+
cart_hess = np.dot(b_mat.T, np.dot(int_approx_hess, b_mat))
|
|
538
|
+
|
|
539
|
+
# Add three-body contribution (specific to D4)
|
|
540
|
+
three_body_hess = self.calculate_three_body_term(coord, element_list, charges, cn)
|
|
541
|
+
cart_hess += three_body_hess
|
|
542
|
+
|
|
543
|
+
# Handle NaN values
|
|
544
|
+
cart_hess = np.nan_to_num(cart_hess, nan=0.0)
|
|
545
|
+
|
|
546
|
+
# Ensure Hessian is symmetric
|
|
547
|
+
n = len(coord) * 3
|
|
548
|
+
for i in range(n):
|
|
549
|
+
for j in range(i):
|
|
550
|
+
avg = (cart_hess[i, j] + cart_hess[j, i]) / 2
|
|
551
|
+
cart_hess[i, j] = avg
|
|
552
|
+
cart_hess[j, i] = avg
|
|
553
|
+
|
|
554
|
+
# Project out translational and rotational modes
|
|
555
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(cart_hess, element_list, coord)
|
|
556
|
+
|
|
557
|
+
print("D4 dispersion correction applied successfully")
|
|
558
|
+
return hess_proj
|
|
559
|
+
|