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,706 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib, 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, D3Parameters
|
|
7
|
+
|
|
8
|
+
class SwartD3ApproxHessian:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
#Swart's Model Hessian augmented with D3 dispersion
|
|
11
|
+
#ref.: M. Swart, F. M. Bickelhaupt, Int. J. Quantum Chem., 2006, 106, 2536–2544.
|
|
12
|
+
#ref.: S. Grimme, J. Antony, S. Ehrlich, H. Krieg, J. Chem. Phys., 2010, 132, 154104
|
|
13
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
14
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
15
|
+
self.kd = 2.00
|
|
16
|
+
|
|
17
|
+
self.kr = 0.35
|
|
18
|
+
self.kf = 0.15
|
|
19
|
+
self.kt = 0.005
|
|
20
|
+
|
|
21
|
+
self.cutoff = 70.0
|
|
22
|
+
self.eps = 1.0e-12
|
|
23
|
+
|
|
24
|
+
# D3 parameters
|
|
25
|
+
self.d3_params = D3Parameters()
|
|
26
|
+
|
|
27
|
+
# Cyano group parameters
|
|
28
|
+
self.cn_kr = 0.70 # Enhanced force constant for C≡N triple bond
|
|
29
|
+
self.cn_kf = 0.20 # Enhanced force constant for angles involving C≡N
|
|
30
|
+
self.cn_kt = 0.002 # Reduced force constant for torsions involving C≡N
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
def detect_cyano_groups(self, coord, element_list):
|
|
34
|
+
"""Detect C≡N triple bonds in the structure"""
|
|
35
|
+
cyano_atoms = [] # List of (C_idx, N_idx) tuples
|
|
36
|
+
|
|
37
|
+
for i in range(len(coord)):
|
|
38
|
+
if element_list[i] != 'C':
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
for j in range(len(coord)):
|
|
42
|
+
if i == j or element_list[j] != 'N':
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
# Calculate distance between C and N
|
|
46
|
+
x_ij = coord[i][0] - coord[j][0]
|
|
47
|
+
y_ij = coord[i][1] - coord[j][1]
|
|
48
|
+
z_ij = coord[i][2] - coord[j][2]
|
|
49
|
+
r_ij = np.sqrt(x_ij**2 + y_ij**2 + z_ij**2)
|
|
50
|
+
|
|
51
|
+
# Check if distance is close to a triple bond length
|
|
52
|
+
cn_triple_bond = triple_covalent_radii_lib('C') + triple_covalent_radii_lib('N')
|
|
53
|
+
|
|
54
|
+
if abs(r_ij - cn_triple_bond) < 0.3: # Within 0.3 bohr of ideal length
|
|
55
|
+
# Check if C is connected to only one other atom (besides N)
|
|
56
|
+
connections_to_c = 0
|
|
57
|
+
for k in range(len(coord)):
|
|
58
|
+
if k == i or k == j:
|
|
59
|
+
continue
|
|
60
|
+
|
|
61
|
+
x_ik = coord[i][0] - coord[k][0]
|
|
62
|
+
y_ik = coord[i][1] - coord[k][1]
|
|
63
|
+
z_ik = coord[i][2] - coord[k][2]
|
|
64
|
+
r_ik = np.sqrt(x_ik**2 + y_ik**2 + z_ik**2)
|
|
65
|
+
|
|
66
|
+
cov_dist = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
67
|
+
if r_ik < 1.3 * cov_dist: # Using 1.3 as a factor to account for bond length variations
|
|
68
|
+
connections_to_c += 1
|
|
69
|
+
|
|
70
|
+
# If C has only one other connection, it's likely a terminal cyano group
|
|
71
|
+
if connections_to_c <= 1:
|
|
72
|
+
cyano_atoms.append((i, j))
|
|
73
|
+
|
|
74
|
+
return cyano_atoms
|
|
75
|
+
|
|
76
|
+
def calc_force_const(self, alpha, covalent_length, distance):
|
|
77
|
+
force_const = np.exp(-1 * alpha * (distance / covalent_length - 1.0))
|
|
78
|
+
return force_const
|
|
79
|
+
|
|
80
|
+
def d3_damping_function(self, r_ij, r0, order=6):
|
|
81
|
+
"""D3 rational damping function"""
|
|
82
|
+
a1 = self.d3_params.a1
|
|
83
|
+
a2 = self.d3_params.a2
|
|
84
|
+
|
|
85
|
+
if order == 6:
|
|
86
|
+
return 1.0 / (1.0 + 6.0 * (r_ij / (a1 * r0)) ** a2)
|
|
87
|
+
elif order == 8:
|
|
88
|
+
return 1.0 / (1.0 + 6.0 * (r_ij / (a2 * r0)) ** a1)
|
|
89
|
+
return 0.0
|
|
90
|
+
|
|
91
|
+
def get_d3_parameters(self, elem1, elem2):
|
|
92
|
+
"""Get D3 parameters for a pair of elements"""
|
|
93
|
+
# Get R4/R2 values
|
|
94
|
+
r4r2_1 = self.d3_params.get_r4r2(elem1)
|
|
95
|
+
r4r2_2 = self.d3_params.get_r4r2(elem2)
|
|
96
|
+
|
|
97
|
+
# C6 coefficients based on averaging rules
|
|
98
|
+
c6_1 = self.d3_params.get_r4r2(elem1) ** 2
|
|
99
|
+
c6_2 = self.d3_params.get_r4r2(elem2) ** 2
|
|
100
|
+
c6_param = np.sqrt(c6_1 * c6_2)
|
|
101
|
+
|
|
102
|
+
# C8 coefficients using r^4/r^2 ratio
|
|
103
|
+
c8_param = 3.0 * c6_param * np.sqrt(r4r2_1 * r4r2_2)
|
|
104
|
+
|
|
105
|
+
# r0 parameter (combined vdW radii)
|
|
106
|
+
r0_param = np.sqrt(UFF_VDW_distance_lib(elem1) * UFF_VDW_distance_lib(elem2))
|
|
107
|
+
|
|
108
|
+
return c6_param, c8_param, r0_param
|
|
109
|
+
|
|
110
|
+
def calc_d3_force_const(self, r_ij, c6_param, c8_param, r0_param):
|
|
111
|
+
"""Calculate D3 dispersion force constant"""
|
|
112
|
+
s6 = self.d3_params.s6
|
|
113
|
+
s8 = self.d3_params.s8
|
|
114
|
+
|
|
115
|
+
# Apply damping functions
|
|
116
|
+
damp6 = self.d3_damping_function(r_ij, r0_param, order=6)
|
|
117
|
+
damp8 = self.d3_damping_function(r_ij, r0_param, order=8)
|
|
118
|
+
|
|
119
|
+
# Energy terms (negative because dispersion is attractive)
|
|
120
|
+
e6 = -s6 * c6_param / r_ij ** 6 * damp6
|
|
121
|
+
e8 = -s8 * c8_param / r_ij ** 8 * damp8
|
|
122
|
+
|
|
123
|
+
# Force constant is the second derivative of energy
|
|
124
|
+
# Simplified approximation of second derivatives
|
|
125
|
+
fc6 = s6 * c6_param * (42.0 / r_ij ** 8) * damp6
|
|
126
|
+
fc8 = s8 * c8_param * (72.0 / r_ij ** 10) * damp8
|
|
127
|
+
|
|
128
|
+
return fc6 + fc8
|
|
129
|
+
|
|
130
|
+
def swart_bond(self, coord, element_list):
|
|
131
|
+
# Detect cyano groups
|
|
132
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
133
|
+
cyano_set = set()
|
|
134
|
+
for c_idx, n_idx in cyano_atoms:
|
|
135
|
+
cyano_set.add(c_idx)
|
|
136
|
+
cyano_set.add(n_idx)
|
|
137
|
+
|
|
138
|
+
for i in range(len(coord)):
|
|
139
|
+
for j in range(i):
|
|
140
|
+
|
|
141
|
+
x_ij = coord[i][0] - coord[j][0]
|
|
142
|
+
y_ij = coord[i][1] - coord[j][1]
|
|
143
|
+
z_ij = coord[i][2] - coord[j][2]
|
|
144
|
+
r_ij_2 = x_ij**2 + y_ij**2 + z_ij**2
|
|
145
|
+
r_ij = np.sqrt(r_ij_2)
|
|
146
|
+
covalent_length = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
147
|
+
|
|
148
|
+
# Get D3 parameters
|
|
149
|
+
c6_param, c8_param, r0_param = self.get_d3_parameters(element_list[i], element_list[j])
|
|
150
|
+
|
|
151
|
+
# Calculate D3 dispersion contribution
|
|
152
|
+
d3_force_const = self.calc_d3_force_const(r_ij, c6_param, c8_param, r0_param)
|
|
153
|
+
|
|
154
|
+
# Check if this is a cyano bond
|
|
155
|
+
is_cyano_bond = False
|
|
156
|
+
for c_idx, n_idx in cyano_atoms:
|
|
157
|
+
if (i == c_idx and j == n_idx) or (i == n_idx and j == c_idx):
|
|
158
|
+
is_cyano_bond = True
|
|
159
|
+
break
|
|
160
|
+
|
|
161
|
+
# Apply appropriate force constant
|
|
162
|
+
if is_cyano_bond:
|
|
163
|
+
# Special force constant for C≡N triple bond
|
|
164
|
+
g_mm = self.cn_kr * self.calc_force_const(1.0, covalent_length, r_ij) + self.kd * d3_force_const
|
|
165
|
+
else:
|
|
166
|
+
# Regular Swart force constant with D3 dispersion
|
|
167
|
+
g_mm = self.kr * self.calc_force_const(1.0, covalent_length, r_ij) + self.kd * d3_force_const
|
|
168
|
+
|
|
169
|
+
# Calculate Hessian components
|
|
170
|
+
hess_xx = g_mm * x_ij ** 2 / r_ij_2
|
|
171
|
+
hess_xy = g_mm * x_ij * y_ij / r_ij_2
|
|
172
|
+
hess_xz = g_mm * x_ij * z_ij / r_ij_2
|
|
173
|
+
hess_yy = g_mm * y_ij ** 2 / r_ij_2
|
|
174
|
+
hess_yz = g_mm * y_ij * z_ij / r_ij_2
|
|
175
|
+
hess_zz = g_mm * z_ij ** 2 / r_ij_2
|
|
176
|
+
|
|
177
|
+
# Fill the Hessian matrix
|
|
178
|
+
self.cart_hess[i * 3][i * 3] += hess_xx
|
|
179
|
+
self.cart_hess[i * 3 + 1][i * 3] += hess_xy
|
|
180
|
+
self.cart_hess[i * 3 + 1][i * 3 + 1] += hess_yy
|
|
181
|
+
self.cart_hess[i * 3 + 2][i * 3] += hess_xz
|
|
182
|
+
self.cart_hess[i * 3 + 2][i * 3 + 1] += hess_yz
|
|
183
|
+
self.cart_hess[i * 3 + 2][i * 3 + 2] += hess_zz
|
|
184
|
+
|
|
185
|
+
self.cart_hess[j * 3][j * 3] += hess_xx
|
|
186
|
+
self.cart_hess[j * 3 + 1][j * 3] += hess_xy
|
|
187
|
+
self.cart_hess[j * 3 + 1][j * 3 + 1] += hess_yy
|
|
188
|
+
self.cart_hess[j * 3 + 2][j * 3] += hess_xz
|
|
189
|
+
self.cart_hess[j * 3 + 2][j * 3 + 1] += hess_yz
|
|
190
|
+
self.cart_hess[j * 3 + 2][j * 3 + 2] += hess_zz
|
|
191
|
+
|
|
192
|
+
self.cart_hess[i * 3][j * 3] -= hess_xx
|
|
193
|
+
self.cart_hess[i * 3][j * 3 + 1] -= hess_xy
|
|
194
|
+
self.cart_hess[i * 3][j * 3 + 2] -= hess_xz
|
|
195
|
+
self.cart_hess[i * 3 + 1][j * 3] -= hess_xy
|
|
196
|
+
self.cart_hess[i * 3 + 1][j * 3 + 1] -= hess_yy
|
|
197
|
+
self.cart_hess[i * 3 + 1][j * 3 + 2] -= hess_yz
|
|
198
|
+
self.cart_hess[i * 3 + 2][j * 3] -= hess_xz
|
|
199
|
+
self.cart_hess[i * 3 + 2][j * 3 + 1] -= hess_yz
|
|
200
|
+
self.cart_hess[i * 3 + 2][j * 3 + 2] -= hess_zz
|
|
201
|
+
|
|
202
|
+
self.cart_hess[j * 3][i * 3] -= hess_xx
|
|
203
|
+
self.cart_hess[j * 3][i * 3 + 1] -= hess_xy
|
|
204
|
+
self.cart_hess[j * 3][i * 3 + 2] -= hess_xz
|
|
205
|
+
self.cart_hess[j * 3 + 1][i * 3] -= hess_xy
|
|
206
|
+
self.cart_hess[j * 3 + 1][i * 3 + 1] -= hess_yy
|
|
207
|
+
self.cart_hess[j * 3 + 1][i * 3 + 2] -= hess_yz
|
|
208
|
+
self.cart_hess[j * 3 + 2][i * 3] -= hess_xz
|
|
209
|
+
self.cart_hess[j * 3 + 2][i * 3 + 1] -= hess_yz
|
|
210
|
+
self.cart_hess[j * 3 + 2][i * 3 + 2] -= hess_zz
|
|
211
|
+
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
def swart_angle(self, coord, element_list):
|
|
215
|
+
"""Calculate angle bending contributions to the Hessian with D3 dispersion"""
|
|
216
|
+
# Detect cyano groups
|
|
217
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
218
|
+
cyano_set = set()
|
|
219
|
+
for c_idx, n_idx in cyano_atoms:
|
|
220
|
+
cyano_set.add(c_idx)
|
|
221
|
+
cyano_set.add(n_idx)
|
|
222
|
+
|
|
223
|
+
for i in range(len(coord)):
|
|
224
|
+
for j in range(len(coord)):
|
|
225
|
+
if i == j:
|
|
226
|
+
continue
|
|
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_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
233
|
+
|
|
234
|
+
# Get D3 parameters for i-j pair
|
|
235
|
+
c6_ij, c8_ij, r0_ij = self.get_d3_parameters(element_list[i], element_list[j])
|
|
236
|
+
|
|
237
|
+
for k in range(j):
|
|
238
|
+
if i == k:
|
|
239
|
+
continue
|
|
240
|
+
x_ik = coord[i][0] - coord[k][0]
|
|
241
|
+
y_ik = coord[i][1] - coord[k][1]
|
|
242
|
+
z_ik = coord[i][2] - coord[k][2]
|
|
243
|
+
r_ik_2 = x_ik**2 + y_ik**2 + z_ik**2
|
|
244
|
+
r_ik = np.sqrt(r_ik_2)
|
|
245
|
+
covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
246
|
+
|
|
247
|
+
# Check for linear arrangement (cos_theta ~ 1.0)
|
|
248
|
+
error_check = x_ij * x_ik + y_ij * y_ik + z_ij * z_ik
|
|
249
|
+
error_check = error_check / (r_ij * r_ik)
|
|
250
|
+
|
|
251
|
+
if abs(error_check - 1.0) < self.eps:
|
|
252
|
+
continue
|
|
253
|
+
|
|
254
|
+
x_jk = coord[j][0] - coord[k][0]
|
|
255
|
+
y_jk = coord[j][1] - coord[k][1]
|
|
256
|
+
z_jk = coord[j][2] - coord[k][2]
|
|
257
|
+
r_jk_2 = x_jk**2 + y_jk**2 + z_jk**2
|
|
258
|
+
r_jk = np.sqrt(r_jk_2)
|
|
259
|
+
|
|
260
|
+
# Get D3 parameters for i-k pair
|
|
261
|
+
c6_ik, c8_ik, r0_ik = self.get_d3_parameters(element_list[i], element_list[k])
|
|
262
|
+
|
|
263
|
+
# Calculate D3 dispersion contributions
|
|
264
|
+
d3_ij = self.calc_d3_force_const(r_ij, c6_ij, c8_ij, r0_ij)
|
|
265
|
+
d3_ik = self.calc_d3_force_const(r_ik, c6_ik, c8_ik, r0_ik)
|
|
266
|
+
|
|
267
|
+
# Calculate bond force constants with D3 dispersion
|
|
268
|
+
g_ij = self.calc_force_const(1.0, covalent_length_ij, r_ij) + 0.5 * self.kd * d3_ij
|
|
269
|
+
g_ik = self.calc_force_const(1.0, covalent_length_ik, r_ik) + 0.5 * self.kd * d3_ik
|
|
270
|
+
|
|
271
|
+
# Check if angle involves cyano group
|
|
272
|
+
is_cyano_angle = (i in cyano_set or j in cyano_set or k in cyano_set)
|
|
273
|
+
|
|
274
|
+
# Apply appropriate force constant
|
|
275
|
+
if is_cyano_angle:
|
|
276
|
+
# Special force constant for angles involving cyano groups
|
|
277
|
+
g_jk = self.cn_kf * g_ij * g_ik
|
|
278
|
+
else:
|
|
279
|
+
# Regular Swart force constant
|
|
280
|
+
g_jk = self.kf * g_ij * g_ik
|
|
281
|
+
|
|
282
|
+
# Calculate cross product for sin(theta)
|
|
283
|
+
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
|
|
284
|
+
|
|
285
|
+
if r_cross_2 < 1.0e-12:
|
|
286
|
+
r_cross = 0.0
|
|
287
|
+
else:
|
|
288
|
+
r_cross = np.sqrt(r_cross_2)
|
|
289
|
+
|
|
290
|
+
if r_ik > self.eps and r_ij > self.eps and r_jk > self.eps:
|
|
291
|
+
cos_theta = (r_ij_2 + r_ik_2 - r_jk_2) / (2.0 * r_ij * r_ik)
|
|
292
|
+
sin_theta = r_cross / (r_ij * r_ik)
|
|
293
|
+
|
|
294
|
+
dot_product_r_ij_r_ik = x_ij * x_ik + y_ij * y_ik + z_ij * z_ik
|
|
295
|
+
|
|
296
|
+
if sin_theta > self.eps: # non-linear
|
|
297
|
+
# Calculate derivatives for non-linear case
|
|
298
|
+
s_xj = (x_ij / r_ij * cos_theta - x_ik / r_ik) / (r_ij * sin_theta)
|
|
299
|
+
s_yj = (y_ij / r_ij * cos_theta - y_ik / r_ik) / (r_ij * sin_theta)
|
|
300
|
+
s_zj = (z_ij / r_ij * cos_theta - z_ik / r_ik) / (r_ij * sin_theta)
|
|
301
|
+
|
|
302
|
+
s_xk = (x_ik / r_ik * cos_theta - x_ij / r_ij) / (r_ik * sin_theta)
|
|
303
|
+
s_yk = (y_ik / r_ik * cos_theta - y_ij / r_ij) / (r_ik * sin_theta)
|
|
304
|
+
s_zk = (z_ik / r_ik * cos_theta - z_ij / r_ij) / (r_ik * sin_theta)
|
|
305
|
+
|
|
306
|
+
s_xi = -1 * s_xj - s_xk
|
|
307
|
+
s_yi = -1 * s_yj - s_yk
|
|
308
|
+
s_zi = -1 * s_zj - s_zk
|
|
309
|
+
|
|
310
|
+
s_j = [s_xj, s_yj, s_zj]
|
|
311
|
+
s_k = [s_xk, s_yk, s_zk]
|
|
312
|
+
s_i = [s_xi, s_yi, s_zi]
|
|
313
|
+
|
|
314
|
+
# Update Hessian for non-linear case
|
|
315
|
+
for l in range(3):
|
|
316
|
+
for m in range(3):
|
|
317
|
+
#-------------------------------------
|
|
318
|
+
if i > j:
|
|
319
|
+
tmp_val = g_jk * s_i[l] * s_j[m]
|
|
320
|
+
self.cart_hess[i * 3 + l][j * 3 + m] += tmp_val
|
|
321
|
+
else:
|
|
322
|
+
tmp_val = g_jk * s_j[l] * s_i[m]
|
|
323
|
+
self.cart_hess[j * 3 + l][i * 3 + m] += tmp_val
|
|
324
|
+
|
|
325
|
+
#-------------------------------------
|
|
326
|
+
if i > k:
|
|
327
|
+
tmp_val = g_jk * s_i[l] * s_k[m]
|
|
328
|
+
self.cart_hess[i * 3 + l][k * 3 + m] += tmp_val
|
|
329
|
+
else:
|
|
330
|
+
tmp_val = g_jk * s_k[l] * s_i[m]
|
|
331
|
+
self.cart_hess[k * 3 + l][i * 3 + m] += tmp_val
|
|
332
|
+
|
|
333
|
+
#-------------------------------------
|
|
334
|
+
if j > k:
|
|
335
|
+
tmp_val = g_jk * s_j[l] * s_k[m]
|
|
336
|
+
self.cart_hess[j * 3 + l][k * 3 + m] += tmp_val
|
|
337
|
+
else:
|
|
338
|
+
tmp_val = g_jk * s_k[l] * s_j[m]
|
|
339
|
+
self.cart_hess[k * 3 + l][j * 3 + m] += tmp_val
|
|
340
|
+
#-------------------------------------
|
|
341
|
+
|
|
342
|
+
# Update diagonal blocks
|
|
343
|
+
for l in range(3):
|
|
344
|
+
for m in range(l):
|
|
345
|
+
tmp_val_1 = g_jk * s_j[l] * s_j[m]
|
|
346
|
+
tmp_val_2 = g_jk * s_i[l] * s_i[m]
|
|
347
|
+
tmp_val_3 = g_jk * s_k[l] * s_k[m]
|
|
348
|
+
|
|
349
|
+
self.cart_hess[j * 3 + l][j * 3 + m] += tmp_val_1
|
|
350
|
+
self.cart_hess[i * 3 + l][i * 3 + m] += tmp_val_2
|
|
351
|
+
self.cart_hess[k * 3 + l][k * 3 + m] += tmp_val_3
|
|
352
|
+
|
|
353
|
+
else: # linear
|
|
354
|
+
# Special handling for linear arrangements
|
|
355
|
+
if abs(y_ij) < self.eps and abs(z_ij) < self.eps:
|
|
356
|
+
x_1 = -1 * y_ij
|
|
357
|
+
y_1 = x_ij
|
|
358
|
+
z_1 = 0.0
|
|
359
|
+
x_2 = -1 * x_ij * z_ij
|
|
360
|
+
y_2 = -1 * y_ij * z_ij
|
|
361
|
+
z_2 = x_ij ** 2 + y_ij ** 2
|
|
362
|
+
else:
|
|
363
|
+
x_1 = 1.0
|
|
364
|
+
y_1 = 0.0
|
|
365
|
+
z_1 = 0.0
|
|
366
|
+
x_2 = 0.0
|
|
367
|
+
y_2 = 1.0
|
|
368
|
+
z_2 = 0.0
|
|
369
|
+
|
|
370
|
+
x = [x_1, x_2]
|
|
371
|
+
y = [y_1, y_2]
|
|
372
|
+
z = [z_1, z_2]
|
|
373
|
+
|
|
374
|
+
# Iterate over two perpendicular directions
|
|
375
|
+
for ii in range(2):
|
|
376
|
+
r_1 = np.sqrt(x[ii] ** 2 + y[ii] ** 2 + z[ii] ** 2)
|
|
377
|
+
cos_theta_x = x[ii] / r_1
|
|
378
|
+
cos_theta_y = y[ii] / r_1
|
|
379
|
+
cos_theta_z = z[ii] / r_1
|
|
380
|
+
|
|
381
|
+
s_xj = -1 * cos_theta_x / r_ij
|
|
382
|
+
s_yj = -1 * cos_theta_y / r_ij
|
|
383
|
+
s_zj = -1 * cos_theta_z / r_ij
|
|
384
|
+
s_xk = -1 * cos_theta_x / r_ik
|
|
385
|
+
s_yk = -1 * cos_theta_y / r_ik
|
|
386
|
+
s_zk = -1 * cos_theta_z / r_ik
|
|
387
|
+
|
|
388
|
+
s_xi = -1 * s_xj - s_xk
|
|
389
|
+
s_yi = -1 * s_yj - s_yk
|
|
390
|
+
s_zi = -1 * s_zj - s_zk
|
|
391
|
+
|
|
392
|
+
s_j = [s_xj, s_yj, s_zj]
|
|
393
|
+
s_k = [s_xk, s_yk, s_zk]
|
|
394
|
+
s_i = [s_xi, s_yi, s_zi]
|
|
395
|
+
|
|
396
|
+
# Update Hessian for linear case
|
|
397
|
+
for l in range(3):
|
|
398
|
+
for m in range(3):
|
|
399
|
+
#-------------------------------------
|
|
400
|
+
if i > j:
|
|
401
|
+
tmp_val = g_jk * s_i[l] * s_j[m]
|
|
402
|
+
self.cart_hess[i * 3 + l][j * 3 + m] += tmp_val
|
|
403
|
+
else:
|
|
404
|
+
tmp_val = g_jk * s_j[l] * s_i[m]
|
|
405
|
+
self.cart_hess[j * 3 + l][i * 3 + m] += tmp_val
|
|
406
|
+
#-------------------------------------
|
|
407
|
+
if i > k:
|
|
408
|
+
tmp_val = g_jk * s_i[l] * s_k[m]
|
|
409
|
+
self.cart_hess[i * 3 + l][k * 3 + m] += tmp_val
|
|
410
|
+
else:
|
|
411
|
+
tmp_val = g_jk * s_k[l] * s_i[m]
|
|
412
|
+
self.cart_hess[k * 3 + l][i * 3 + m] += tmp_val
|
|
413
|
+
#-------------------------------------
|
|
414
|
+
if j > k:
|
|
415
|
+
tmp_val = g_jk * s_j[l] * s_k[m]
|
|
416
|
+
self.cart_hess[j * 3 + l][k * 3 + m] += tmp_val
|
|
417
|
+
else:
|
|
418
|
+
tmp_val = g_jk * s_k[l] * s_j[m]
|
|
419
|
+
self.cart_hess[k * 3 + l][j * 3 + m] += tmp_val
|
|
420
|
+
#-------------------------------------
|
|
421
|
+
|
|
422
|
+
# Update diagonal blocks for linear case
|
|
423
|
+
for l in range(3):
|
|
424
|
+
for m in range(l):
|
|
425
|
+
tmp_val_1 = g_jk * s_j[l] * s_j[m]
|
|
426
|
+
tmp_val_2 = g_jk * s_i[l] * s_i[m]
|
|
427
|
+
tmp_val_3 = g_jk * s_k[l] * s_k[m]
|
|
428
|
+
|
|
429
|
+
self.cart_hess[j * 3 + l][j * 3 + m] += tmp_val_1
|
|
430
|
+
self.cart_hess[i * 3 + l][i * 3 + m] += tmp_val_2
|
|
431
|
+
self.cart_hess[k * 3 + l][k * 3 + m] += tmp_val_3
|
|
432
|
+
else:
|
|
433
|
+
pass # Skip if any distance is too small
|
|
434
|
+
|
|
435
|
+
# Make the Hessian symmetric for angle terms
|
|
436
|
+
n_basis = len(coord) * 3
|
|
437
|
+
for i in range(n_basis):
|
|
438
|
+
for j in range(i):
|
|
439
|
+
if abs(self.cart_hess[i][j] - self.cart_hess[j][i]) > 1.0e-10:
|
|
440
|
+
avg = (self.cart_hess[i][j] + self.cart_hess[j][i]) / 2.0
|
|
441
|
+
self.cart_hess[i][j] = avg
|
|
442
|
+
self.cart_hess[j][i] = avg
|
|
443
|
+
|
|
444
|
+
return
|
|
445
|
+
|
|
446
|
+
def swart_dihedral_angle(self, coord, element_list):
|
|
447
|
+
"""Calculate dihedral angle contributions to the Hessian with D3 dispersion"""
|
|
448
|
+
# Detect cyano groups
|
|
449
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
450
|
+
cyano_set = set()
|
|
451
|
+
for c_idx, n_idx in cyano_atoms:
|
|
452
|
+
cyano_set.add(c_idx)
|
|
453
|
+
cyano_set.add(n_idx)
|
|
454
|
+
|
|
455
|
+
for j in range(len(coord)):
|
|
456
|
+
t_xyz_2 = coord[j]
|
|
457
|
+
|
|
458
|
+
for k in range(len(coord)):
|
|
459
|
+
if j >= k:
|
|
460
|
+
continue
|
|
461
|
+
t_xyz_3 = coord[k]
|
|
462
|
+
for i in range(len(coord)):
|
|
463
|
+
ij = (len(coord) * j) + (i + 1)
|
|
464
|
+
if i >= j:
|
|
465
|
+
continue
|
|
466
|
+
if i >= k:
|
|
467
|
+
continue
|
|
468
|
+
|
|
469
|
+
t_xyz_1 = coord[i]
|
|
470
|
+
|
|
471
|
+
for l in range(len(coord)):
|
|
472
|
+
kl = (len(coord) * k) + (l + 1)
|
|
473
|
+
|
|
474
|
+
if ij <= kl:
|
|
475
|
+
continue
|
|
476
|
+
if l >= i:
|
|
477
|
+
continue
|
|
478
|
+
if l >= j:
|
|
479
|
+
continue
|
|
480
|
+
if l >= k:
|
|
481
|
+
continue
|
|
482
|
+
|
|
483
|
+
t_xyz_4 = coord[l]
|
|
484
|
+
r_ij = coord[i] - coord[j]
|
|
485
|
+
r_jk = coord[j] - coord[k]
|
|
486
|
+
r_kl = coord[k] - coord[l]
|
|
487
|
+
|
|
488
|
+
covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
489
|
+
covalent_length_jk = covalent_radii_lib(element_list[j]) + covalent_radii_lib(element_list[k])
|
|
490
|
+
covalent_length_kl = covalent_radii_lib(element_list[k]) + covalent_radii_lib(element_list[l])
|
|
491
|
+
|
|
492
|
+
# Calculate vector magnitudes
|
|
493
|
+
r_ij_2 = np.sum(r_ij ** 2)
|
|
494
|
+
r_jk_2 = np.sum(r_jk ** 2)
|
|
495
|
+
r_kl_2 = np.sum(r_kl ** 2)
|
|
496
|
+
norm_r_ij = np.sqrt(r_ij_2)
|
|
497
|
+
norm_r_jk = np.sqrt(r_jk_2)
|
|
498
|
+
norm_r_kl = np.sqrt(r_kl_2)
|
|
499
|
+
|
|
500
|
+
# Skip if angle is too shallow (less than 35 degrees)
|
|
501
|
+
a35 = (35.0/180)* np.pi
|
|
502
|
+
cosfi_max = np.cos(a35)
|
|
503
|
+
cosfi2 = np.dot(r_ij, r_jk) / np.sqrt(r_ij_2 * r_jk_2)
|
|
504
|
+
if abs(cosfi2) > cosfi_max:
|
|
505
|
+
continue
|
|
506
|
+
cosfi3 = np.dot(r_kl, r_jk) / np.sqrt(r_kl_2 * r_jk_2)
|
|
507
|
+
if abs(cosfi3) > cosfi_max:
|
|
508
|
+
continue
|
|
509
|
+
|
|
510
|
+
# Get D3 parameters for each atom pair
|
|
511
|
+
c6_ij, c8_ij, r0_ij = self.get_d3_parameters(element_list[i], element_list[j])
|
|
512
|
+
c6_jk, c8_jk, r0_jk = self.get_d3_parameters(element_list[j], element_list[k])
|
|
513
|
+
c6_kl, c8_kl, r0_kl = self.get_d3_parameters(element_list[k], element_list[l])
|
|
514
|
+
|
|
515
|
+
# Calculate D3 dispersion contributions
|
|
516
|
+
d3_ij = self.calc_d3_force_const(norm_r_ij, c6_ij, c8_ij, r0_ij)
|
|
517
|
+
d3_jk = self.calc_d3_force_const(norm_r_jk, c6_jk, c8_jk, r0_jk)
|
|
518
|
+
d3_kl = self.calc_d3_force_const(norm_r_kl, c6_kl, c8_kl, r0_kl)
|
|
519
|
+
|
|
520
|
+
# Calculate bond force constants with D3 dispersion
|
|
521
|
+
g_ij = self.calc_force_const(1.0, covalent_length_ij, norm_r_ij) + 0.5 * self.kd * d3_ij
|
|
522
|
+
g_jk = self.calc_force_const(1.0, covalent_length_jk, norm_r_jk) + 0.5 * self.kd * d3_jk
|
|
523
|
+
g_kl = self.calc_force_const(1.0, covalent_length_kl, norm_r_kl) + 0.5 * self.kd * d3_kl
|
|
524
|
+
|
|
525
|
+
# Check if torsion involves cyano group
|
|
526
|
+
is_cyano_torsion = False
|
|
527
|
+
if i in cyano_set or j in cyano_set or k in cyano_set or l in cyano_set:
|
|
528
|
+
is_cyano_torsion = True
|
|
529
|
+
|
|
530
|
+
# Adjust force constant for cyano groups - they have flatter torsional potentials
|
|
531
|
+
if is_cyano_torsion:
|
|
532
|
+
t_ij = self.cn_kt * g_ij * g_jk * g_kl
|
|
533
|
+
else:
|
|
534
|
+
t_ij = self.kt * g_ij * g_jk * g_kl
|
|
535
|
+
|
|
536
|
+
# Calculate torsion angle and derivatives
|
|
537
|
+
t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
|
|
538
|
+
tau, c = torsion2(t_xyz)
|
|
539
|
+
s_i = c[0]
|
|
540
|
+
s_j = c[1]
|
|
541
|
+
s_k = c[2]
|
|
542
|
+
s_l = c[3]
|
|
543
|
+
|
|
544
|
+
# Update Hessian with torsional contributions
|
|
545
|
+
for n in range(3):
|
|
546
|
+
for m in range(3):
|
|
547
|
+
self.cart_hess[3 * i + n][3 * j + m] += t_ij * s_i[n] * s_j[m]
|
|
548
|
+
self.cart_hess[3 * i + n][3 * k + m] += t_ij * s_i[n] * s_k[m]
|
|
549
|
+
self.cart_hess[3 * i + n][3 * l + m] += t_ij * s_i[n] * s_l[m]
|
|
550
|
+
self.cart_hess[3 * j + n][3 * k + m] += t_ij * s_j[n] * s_k[m]
|
|
551
|
+
self.cart_hess[3 * j + n][3 * l + m] += t_ij * s_j[n] * s_l[m]
|
|
552
|
+
self.cart_hess[3 * k + n][3 * l + m] += t_ij * s_k[n] * s_l[m]
|
|
553
|
+
|
|
554
|
+
# Update diagonal blocks
|
|
555
|
+
for n in range(3):
|
|
556
|
+
for m in range(n):
|
|
557
|
+
self.cart_hess[3 * i + n][3 * i + m] += t_ij * s_i[n] * s_i[m]
|
|
558
|
+
self.cart_hess[3 * j + n][3 * j + m] += t_ij * s_j[n] * s_j[m]
|
|
559
|
+
self.cart_hess[3 * k + n][3 * k + m] += t_ij * s_k[n] * s_k[m]
|
|
560
|
+
self.cart_hess[3 * l + n][3 * l + m] += t_ij * s_l[n] * s_l[m]
|
|
561
|
+
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
def swart_out_of_plane(self, coord, element_list):
|
|
565
|
+
"""Calculate out-of-plane bending contributions to the Hessian"""
|
|
566
|
+
# Detect cyano groups
|
|
567
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
568
|
+
cyano_set = set()
|
|
569
|
+
for c_idx, n_idx in cyano_atoms:
|
|
570
|
+
cyano_set.add(c_idx)
|
|
571
|
+
cyano_set.add(n_idx)
|
|
572
|
+
|
|
573
|
+
for i in range(len(coord)):
|
|
574
|
+
t_xyz_4 = coord[i]
|
|
575
|
+
for j in range(len(coord)):
|
|
576
|
+
if i >= j:
|
|
577
|
+
continue
|
|
578
|
+
t_xyz_1 = coord[j]
|
|
579
|
+
for k in range(len(coord)):
|
|
580
|
+
ij = (len(coord) * j) + (i + 1)
|
|
581
|
+
if i >= k:
|
|
582
|
+
continue
|
|
583
|
+
if j >= k:
|
|
584
|
+
continue
|
|
585
|
+
t_xyz_2 = coord[k]
|
|
586
|
+
|
|
587
|
+
for l in range(len(coord)):
|
|
588
|
+
kl = (len(coord) * k) + (l + 1)
|
|
589
|
+
if i >= l:
|
|
590
|
+
continue
|
|
591
|
+
if j >= l:
|
|
592
|
+
continue
|
|
593
|
+
if k >= l:
|
|
594
|
+
continue
|
|
595
|
+
if ij <= kl:
|
|
596
|
+
continue
|
|
597
|
+
t_xyz_3 = coord[l]
|
|
598
|
+
|
|
599
|
+
r_ij = coord[i] - coord[j]
|
|
600
|
+
covalent_length_ij = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
601
|
+
|
|
602
|
+
r_ik = coord[i] - coord[k]
|
|
603
|
+
covalent_length_ik = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
604
|
+
|
|
605
|
+
r_il = coord[i] - coord[l]
|
|
606
|
+
covalent_length_il = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[l])
|
|
607
|
+
|
|
608
|
+
r_ij_2 = np.sum(r_ij ** 2)
|
|
609
|
+
r_ik_2 = np.sum(r_ik ** 2)
|
|
610
|
+
r_il_2 = np.sum(r_il ** 2)
|
|
611
|
+
norm_r_ij = np.sqrt(r_ij_2)
|
|
612
|
+
norm_r_ik = np.sqrt(r_ik_2)
|
|
613
|
+
norm_r_il = np.sqrt(r_il_2)
|
|
614
|
+
|
|
615
|
+
# Skip if atoms are nearly collinear
|
|
616
|
+
cosfi2 = np.dot(r_ij, r_ik) / np.sqrt(r_ij_2 * r_ik_2)
|
|
617
|
+
if abs(abs(cosfi2) - 1.0) < 1.0e-1:
|
|
618
|
+
continue
|
|
619
|
+
cosfi3 = np.dot(r_ij, r_il) / np.sqrt(r_ij_2 * r_il_2)
|
|
620
|
+
if abs(abs(cosfi3) - 1.0) < 1.0e-1:
|
|
621
|
+
continue
|
|
622
|
+
cosfi4 = np.dot(r_ik, r_il) / np.sqrt(r_ik_2 * r_il_2)
|
|
623
|
+
if abs(abs(cosfi4) - 1.0) < 1.0e-1:
|
|
624
|
+
continue
|
|
625
|
+
|
|
626
|
+
# Get D3 parameters for each atom pair
|
|
627
|
+
c6_ij, c8_ij, r0_ij = self.get_d3_parameters(element_list[i], element_list[j])
|
|
628
|
+
c6_ik, c8_ik, r0_ik = self.get_d3_parameters(element_list[i], element_list[k])
|
|
629
|
+
c6_il, c8_il, r0_il = self.get_d3_parameters(element_list[i], element_list[l])
|
|
630
|
+
|
|
631
|
+
# Calculate D3 dispersion contributions
|
|
632
|
+
d3_ij = self.calc_d3_force_const(norm_r_ij, c6_ij, c8_ij, r0_ij)
|
|
633
|
+
d3_ik = self.calc_d3_force_const(norm_r_ik, c6_ik, c8_ik, r0_ik)
|
|
634
|
+
d3_il = self.calc_d3_force_const(norm_r_il, c6_il, c8_il, r0_il)
|
|
635
|
+
|
|
636
|
+
# Calculate bond force constants with D3 dispersion
|
|
637
|
+
g_ij = self.calc_force_const(1.0, covalent_length_ij, norm_r_ij) + 0.5 * self.kd * d3_ij
|
|
638
|
+
g_ik = self.calc_force_const(1.0, covalent_length_ik, norm_r_ik) + 0.5 * self.kd * d3_ik
|
|
639
|
+
g_il = self.calc_force_const(1.0, covalent_length_il, norm_r_il) + 0.5 * self.kd * d3_il
|
|
640
|
+
|
|
641
|
+
# Check if any atom is part of a cyano group
|
|
642
|
+
is_cyano_involved = (i in cyano_set or j in cyano_set or
|
|
643
|
+
k in cyano_set or l in cyano_set)
|
|
644
|
+
|
|
645
|
+
# Adjust force constant if cyano group is involved
|
|
646
|
+
if is_cyano_involved:
|
|
647
|
+
t_ij = 0.5 * self.kt * g_ij * g_ik * g_il # Reduce force constant for cyano
|
|
648
|
+
else:
|
|
649
|
+
t_ij = self.kt * g_ij * g_ik * g_il
|
|
650
|
+
|
|
651
|
+
# Calculate out-of-plane angle and derivatives
|
|
652
|
+
t_xyz = np.array([t_xyz_1, t_xyz_2, t_xyz_3, t_xyz_4])
|
|
653
|
+
theta, c = outofplane2(t_xyz)
|
|
654
|
+
|
|
655
|
+
s_i = c[0]
|
|
656
|
+
s_j = c[1]
|
|
657
|
+
s_k = c[2]
|
|
658
|
+
s_l = c[3]
|
|
659
|
+
|
|
660
|
+
# Update Hessian with out-of-plane contributions
|
|
661
|
+
for n in range(3):
|
|
662
|
+
for m in range(3):
|
|
663
|
+
self.cart_hess[i * 3 + n][j * 3 + m] += t_ij * s_i[n] * s_j[m]
|
|
664
|
+
self.cart_hess[i * 3 + n][k * 3 + m] += t_ij * s_i[n] * s_k[m]
|
|
665
|
+
self.cart_hess[i * 3 + n][l * 3 + m] += t_ij * s_i[n] * s_l[m]
|
|
666
|
+
self.cart_hess[j * 3 + n][k * 3 + m] += t_ij * s_j[n] * s_k[m]
|
|
667
|
+
self.cart_hess[j * 3 + n][l * 3 + m] += t_ij * s_j[n] * s_l[m]
|
|
668
|
+
self.cart_hess[k * 3 + n][l * 3 + m] += t_ij * s_k[n] * s_l[m]
|
|
669
|
+
|
|
670
|
+
# Update diagonal blocks
|
|
671
|
+
for n in range(3):
|
|
672
|
+
for m in range(n):
|
|
673
|
+
self.cart_hess[i * 3 + n][i * 3 + m] += t_ij * s_i[n] * s_i[m]
|
|
674
|
+
self.cart_hess[j * 3 + n][j * 3 + m] += t_ij * s_j[n] * s_j[m]
|
|
675
|
+
self.cart_hess[k * 3 + n][k * 3 + m] += t_ij * s_k[n] * s_k[m]
|
|
676
|
+
self.cart_hess[l * 3 + n][l * 3 + m] += t_ij * s_l[n] * s_l[m]
|
|
677
|
+
|
|
678
|
+
return
|
|
679
|
+
|
|
680
|
+
def main(self, coord, element_list, cart_gradient):
|
|
681
|
+
"""Main method to calculate the approximate Hessian using Swart's model with D3 dispersion"""
|
|
682
|
+
print("Generating Swart's approximate hessian with D3 dispersion correction...")
|
|
683
|
+
self.cart_hess = np.zeros((len(coord)*3, len(coord)*3), dtype="float64")
|
|
684
|
+
|
|
685
|
+
self.swart_bond(coord, element_list)
|
|
686
|
+
self.swart_angle(coord, element_list)
|
|
687
|
+
self.swart_dihedral_angle(coord, element_list)
|
|
688
|
+
self.swart_out_of_plane(coord, element_list)
|
|
689
|
+
|
|
690
|
+
# Ensure symmetry of the Hessian matrix
|
|
691
|
+
for i in range(len(coord)*3):
|
|
692
|
+
for j in range(i):
|
|
693
|
+
if abs(self.cart_hess[i][j]) < 1.0e-10:
|
|
694
|
+
self.cart_hess[i][j] = self.cart_hess[j][i]
|
|
695
|
+
elif abs(self.cart_hess[j][i]) < 1.0e-10:
|
|
696
|
+
self.cart_hess[j][i] = self.cart_hess[i][j]
|
|
697
|
+
else:
|
|
698
|
+
# Average if both elements are non-zero but different
|
|
699
|
+
avg = (self.cart_hess[i][j] + self.cart_hess[j][i]) / 2.0
|
|
700
|
+
self.cart_hess[i][j] = avg
|
|
701
|
+
self.cart_hess[j][i] = avg
|
|
702
|
+
|
|
703
|
+
# Project out translational and rotational modes
|
|
704
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
|
|
705
|
+
return hess_proj
|
|
706
|
+
|