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,106 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from multioptpy.Parameters.parameter import GNB_radii_lib
|
|
3
|
+
|
|
4
|
+
class MorseApproxHessian:
|
|
5
|
+
"""
|
|
6
|
+
A simple class to generate a model Hessian based on the second derivative
|
|
7
|
+
of a Morse potential, using GNB_radii_lib for covalent radii to estimate
|
|
8
|
+
equilibrium bond distances. This is a highly simplified illustration.
|
|
9
|
+
|
|
10
|
+
In this version, the covalent radii are obtained from GNB_radii_lib(element).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, De=0.10, a=0.20):
|
|
14
|
+
"""
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
De : float
|
|
18
|
+
Dissociation energy in arbitrary units (e.g., Hartree).
|
|
19
|
+
a : float
|
|
20
|
+
Range parameter for the Morse potential.
|
|
21
|
+
"""
|
|
22
|
+
self.De = De
|
|
23
|
+
self.a = a
|
|
24
|
+
|
|
25
|
+
def estimate_bond_length(self, elem1, elem2):
|
|
26
|
+
"""
|
|
27
|
+
Estimate equilibrium bond length using GNB_radii_lib for each element.
|
|
28
|
+
"""
|
|
29
|
+
r1 = GNB_radii_lib(elem1)
|
|
30
|
+
r2 = GNB_radii_lib(elem2)
|
|
31
|
+
return r1 + r2
|
|
32
|
+
|
|
33
|
+
def compute_morse_second_derivative(self, r_current, r_eq):
|
|
34
|
+
"""
|
|
35
|
+
Compute the second derivative of the Morse potential with respect to r,
|
|
36
|
+
evaluated at r_current.
|
|
37
|
+
|
|
38
|
+
V(r) = De * [1 - exp(-a * (r - r_eq))]^2
|
|
39
|
+
|
|
40
|
+
For simplicity, use a general expanded form for the second derivative:
|
|
41
|
+
d^2V/dr^2 = De * a^2 [ -2 e^{-x} + 4 e^{-2x} ]
|
|
42
|
+
where x = a (r - r_eq).
|
|
43
|
+
"""
|
|
44
|
+
x = self.a * (r_current - r_eq)
|
|
45
|
+
# Expanded form for d^2V/dr^2
|
|
46
|
+
second_derivative = self.De * (self.a ** 2) * (-2.0 * np.exp(-x) + 4.0 * np.exp(-2.0 * x))
|
|
47
|
+
return second_derivative
|
|
48
|
+
|
|
49
|
+
def create_model_hessian(self, coord, element_list):
|
|
50
|
+
"""
|
|
51
|
+
Create a simple Hessian matrix for pairwise bonds as if
|
|
52
|
+
each interaction is an independent Morse potential.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
coord : numpy.ndarray
|
|
57
|
+
Shape (N, 3) array of 3D coordinates for N atoms (in Å).
|
|
58
|
+
element_list : list
|
|
59
|
+
List of element symbols corresponding to the coordinates.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
numpy.ndarray
|
|
64
|
+
Hessian matrix of shape (3N, 3N).
|
|
65
|
+
"""
|
|
66
|
+
n_atoms = len(element_list)
|
|
67
|
+
hessian_size = 3 * n_atoms
|
|
68
|
+
hessian = np.zeros((hessian_size, hessian_size), dtype=float)
|
|
69
|
+
|
|
70
|
+
# Pairwise approach to generate naive bond Hessian elements
|
|
71
|
+
for i in range(n_atoms - 1):
|
|
72
|
+
for j in range(i + 1, n_atoms):
|
|
73
|
+
# Estimate the equilibrium bond length
|
|
74
|
+
r_eq = self.estimate_bond_length(element_list[i], element_list[j])
|
|
75
|
+
|
|
76
|
+
# Current distance
|
|
77
|
+
vec_ij = coord[j] - coord[i]
|
|
78
|
+
dist_ij = np.linalg.norm(vec_ij)
|
|
79
|
+
|
|
80
|
+
# Compute second derivative for the Morse potential
|
|
81
|
+
d2V = self.compute_morse_second_derivative(dist_ij, r_eq)
|
|
82
|
+
|
|
83
|
+
# Handle direction vector
|
|
84
|
+
if dist_ij > 1.0e-12:
|
|
85
|
+
direction = vec_ij / dist_ij
|
|
86
|
+
else:
|
|
87
|
+
direction = np.zeros(3)
|
|
88
|
+
|
|
89
|
+
# Construct the 3x3 block k_ij * (direction outer direction)
|
|
90
|
+
bond_k = d2V * np.outer(direction, direction)
|
|
91
|
+
|
|
92
|
+
# Indices in the full Hessian
|
|
93
|
+
block_i = slice(3 * i, 3 * i + 3)
|
|
94
|
+
block_j = slice(3 * j, 3 * j + 3)
|
|
95
|
+
|
|
96
|
+
# Update diagonal blocks
|
|
97
|
+
hessian[block_i, block_i] += bond_k
|
|
98
|
+
hessian[block_j, block_j] += bond_k
|
|
99
|
+
# Update off-diagonal blocks
|
|
100
|
+
hessian[block_i, block_j] -= bond_k
|
|
101
|
+
hessian[block_j, block_i] -= bond_k
|
|
102
|
+
|
|
103
|
+
# Symmetrize just in case
|
|
104
|
+
hessian = 0.5 * (hessian + hessian.T)
|
|
105
|
+
return hessian
|
|
106
|
+
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import itertools
|
|
3
|
+
|
|
4
|
+
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib
|
|
5
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
6
|
+
from multioptpy.Parameters.parameter import number_element
|
|
7
|
+
from multioptpy.Coordinate.redundant_coordinate import RedundantInternalCoordinates
|
|
8
|
+
from multioptpy.Utils.bond_connectivity import BondConnectivity
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SchlegelApproxHessian:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
#Lindh's approximate hessian
|
|
14
|
+
#ref: Journal of Molecular Structure: THEOCHEM Volumes 398–399, 30 June 1997, Pages 55-61
|
|
15
|
+
#ref: Theoret. Chim. Acta (Berl.) 66, 333–340 (1984)
|
|
16
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
17
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def return_schlegel_const(self, element_1, element_2):
|
|
21
|
+
if type(element_1) is int:
|
|
22
|
+
element_1 = number_element(element_1)
|
|
23
|
+
if type(element_2) is int:
|
|
24
|
+
element_2 = number_element(element_2)
|
|
25
|
+
|
|
26
|
+
parameter_B_matrix = [[0.2573, 0.3401, 0.6937, 0.7126, 0.8335, 0.9491, 0.9491],
|
|
27
|
+
[0.3401, 0.9652, 1.2843, 1.4725, 1.6549, 1.7190, 1.7190],
|
|
28
|
+
[0.6937, 1.2843, 1.6925, 1.8238, 2.1164, 2.3185, 2.3185],
|
|
29
|
+
[0.7126, 1.4725, 1.8238, 2.0203, 2.2137, 2.5206, 2.5206],
|
|
30
|
+
[0.8335, 1.6549, 2.1164, 2.2137, 2.3718, 2.5110, 2.5110],
|
|
31
|
+
[0.9491, 1.7190, 2.3185, 2.5206, 2.5110, 2.5110, 2.5110],
|
|
32
|
+
[0.9491, 1.7190, 2.3185, 2.5206, 2.5110, 2.5110, 2.5110]]# Bohr
|
|
33
|
+
|
|
34
|
+
first_period_table = ["H", "He"]
|
|
35
|
+
second_period_table = ["Li", "Be", "B", "C", "N", "O", "F", "Ne"]
|
|
36
|
+
third_period_table = ["K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br","Kr"]
|
|
37
|
+
fourth_period_table = ["Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc","Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te","I", "Xe"]
|
|
38
|
+
fifth_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"]
|
|
39
|
+
if element_1 in first_period_table:
|
|
40
|
+
idx_1 = 0
|
|
41
|
+
elif element_1 in second_period_table:
|
|
42
|
+
idx_1 = 1
|
|
43
|
+
elif element_1 in third_period_table:
|
|
44
|
+
idx_1 = 2
|
|
45
|
+
elif element_1 in fourth_period_table:
|
|
46
|
+
idx_1 = 3
|
|
47
|
+
elif element_1 in fifth_period_table:
|
|
48
|
+
idx_1 = 4
|
|
49
|
+
else:
|
|
50
|
+
idx_1 = 5
|
|
51
|
+
|
|
52
|
+
if element_2 in first_period_table:
|
|
53
|
+
idx_2 = 0
|
|
54
|
+
elif element_2 in second_period_table:
|
|
55
|
+
idx_2 = 1
|
|
56
|
+
elif element_2 in third_period_table:
|
|
57
|
+
idx_2 = 2
|
|
58
|
+
elif element_2 in fourth_period_table:
|
|
59
|
+
idx_2 = 3
|
|
60
|
+
elif element_2 in fifth_period_table:
|
|
61
|
+
idx_2 = 4
|
|
62
|
+
else:
|
|
63
|
+
idx_2 = 5
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
const_b = parameter_B_matrix[idx_1][idx_2]
|
|
67
|
+
|
|
68
|
+
return const_b
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def guess_schlegel_hessian(self, coord, element_list):
|
|
72
|
+
#coord: cartecian coord, Bohr (atom num × 3)
|
|
73
|
+
BC = BondConnectivity()
|
|
74
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
75
|
+
val_num = len(coord)*3
|
|
76
|
+
connectivity_table = [BC.bond_connect_table(b_c_mat), BC.angle_connect_table(b_c_mat), BC.dihedral_angle_connect_table(b_c_mat)]
|
|
77
|
+
#RIC_approx_diag_hessian = []
|
|
78
|
+
RIC_approx_diag_hessian = [0.0 for i in range(self.RIC_variable_num)]
|
|
79
|
+
RIC_idx_list = [[i[0], i[1]] for i in itertools.combinations([j for j in range(len(coord))] , 2)]
|
|
80
|
+
|
|
81
|
+
for idx_list in connectivity_table:
|
|
82
|
+
for idx in idx_list:
|
|
83
|
+
if len(idx) == 2:
|
|
84
|
+
tmp_idx = sorted([idx[0], idx[1]])
|
|
85
|
+
distance = np.linalg.norm(coord[idx[0]] - coord[idx[1]])
|
|
86
|
+
|
|
87
|
+
elem_1 = element_list[idx[0]]
|
|
88
|
+
elem_2 = element_list[idx[1]]
|
|
89
|
+
const_b = self.return_schlegel_const(elem_1, elem_2)
|
|
90
|
+
tmpnum = RIC_idx_list.index(tmp_idx)
|
|
91
|
+
F = 1.734 / (distance - const_b) ** 3
|
|
92
|
+
RIC_approx_diag_hessian[tmpnum] += F
|
|
93
|
+
|
|
94
|
+
elif len(idx) == 3:
|
|
95
|
+
tmp_idx_1 = sorted([idx[0], idx[1]])
|
|
96
|
+
tmp_idx_2 = sorted([idx[1], idx[2]])
|
|
97
|
+
elem_1 = element_list[idx[0]]
|
|
98
|
+
elem_2 = element_list[idx[1]]
|
|
99
|
+
elem_3 = element_list[idx[2]]
|
|
100
|
+
tmpnum_1 = RIC_idx_list.index(tmp_idx_1)
|
|
101
|
+
tmpnum_2 = RIC_idx_list.index(tmp_idx_2)
|
|
102
|
+
if elem_1 == "H" or elem_3 == "H":
|
|
103
|
+
RIC_approx_diag_hessian[tmpnum_1] += 0.160
|
|
104
|
+
RIC_approx_diag_hessian[tmpnum_2] += 0.160
|
|
105
|
+
else:
|
|
106
|
+
RIC_approx_diag_hessian[tmpnum_1] += 0.250
|
|
107
|
+
RIC_approx_diag_hessian[tmpnum_2] += 0.250
|
|
108
|
+
|
|
109
|
+
elif len(idx) == 4:
|
|
110
|
+
tmp_idx_1 = sorted([idx[0], idx[1]])
|
|
111
|
+
tmp_idx_2 = sorted([idx[1], idx[2]])
|
|
112
|
+
tmp_idx_3 = sorted([idx[2], idx[3]])
|
|
113
|
+
elem_2 = element_list[idx[1]]
|
|
114
|
+
elem_3 = element_list[idx[2]]
|
|
115
|
+
distance = np.linalg.norm(coord[idx[1]] - coord[idx[2]])
|
|
116
|
+
bond_length = covalent_radii_lib(elem_2) + covalent_radii_lib(elem_3)
|
|
117
|
+
tmpnum_1 = RIC_idx_list.index(tmp_idx_1)
|
|
118
|
+
tmpnum_2 = RIC_idx_list.index(tmp_idx_2)
|
|
119
|
+
tmpnum_3 = RIC_idx_list.index(tmp_idx_3)
|
|
120
|
+
RIC_approx_diag_hessian[tmpnum_1] += 0.0023 -1* 0.07 * (distance - bond_length)
|
|
121
|
+
RIC_approx_diag_hessian[tmpnum_2] += 0.0023 -1* 0.07 * (distance - bond_length)
|
|
122
|
+
RIC_approx_diag_hessian[tmpnum_3] += 0.0023 -1* 0.07 * (distance - bond_length)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
RIC_approx_hessian = np.array(np.diag(RIC_approx_diag_hessian), dtype="float64")
|
|
126
|
+
return RIC_approx_hessian
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def main(self, coord, element_list, cart_gradient):
|
|
130
|
+
#coord: Bohr
|
|
131
|
+
print("generating Schlegel's approximate hessian...")
|
|
132
|
+
|
|
133
|
+
b_mat = RedundantInternalCoordinates().B_matrix(coord)
|
|
134
|
+
self.RIC_variable_num = len(b_mat)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
int_approx_hess = self.guess_schlegel_hessian(coord, element_list)
|
|
138
|
+
cart_hess = np.dot(b_mat.T, np.dot(int_approx_hess, b_mat))
|
|
139
|
+
|
|
140
|
+
cart_hess = np.nan_to_num(cart_hess, nan=0.0)
|
|
141
|
+
#eigenvalue, _ = np.linalg.eig(cart_hess)
|
|
142
|
+
#print(sorted(eigenvalue))
|
|
143
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(cart_hess, element_list, coord)
|
|
144
|
+
return hess_proj#cart_hess
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import itertools
|
|
3
|
+
|
|
4
|
+
from multioptpy.Parameters.parameter import UnitValueLib, covalent_radii_lib, UFF_VDW_distance_lib, D3Parameters, 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
|
+
|
|
11
|
+
class SchlegelD3ApproxHessian:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""
|
|
14
|
+
Schlegel's approximate Hessian with D3 dispersion corrections and special handling for cyano groups
|
|
15
|
+
References:
|
|
16
|
+
- Schlegel: Journal of Molecular Structure: THEOCHEM Volumes 398–399, 30 June 1997, Pages 55-61
|
|
17
|
+
- D3: S. Grimme, J. Antony, S. Ehrlich, H. Krieg, J. Chem. Phys., 2010, 132, 154104
|
|
18
|
+
"""
|
|
19
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
20
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
21
|
+
|
|
22
|
+
# D3 dispersion parameters
|
|
23
|
+
self.d3_params = D3Parameters()
|
|
24
|
+
|
|
25
|
+
# Cyano group parameters - enhanced force constants
|
|
26
|
+
self.cn_stretch_factor = 2.0 # Enhance stretch force constants for C≡N triple bond
|
|
27
|
+
self.cn_angle_factor = 1.5 # Enhance angle force constants involving C≡N
|
|
28
|
+
self.cn_torsion_factor = 0.5 # Reduce torsion force constants involving C≡N (more flexible)
|
|
29
|
+
|
|
30
|
+
def detect_cyano_groups(self, coord, element_list):
|
|
31
|
+
"""Detect C≡N triple bonds in the structure"""
|
|
32
|
+
cyano_atoms = [] # List of (C_idx, N_idx) tuples
|
|
33
|
+
|
|
34
|
+
for i in range(len(coord)):
|
|
35
|
+
if element_list[i] != 'C':
|
|
36
|
+
continue
|
|
37
|
+
|
|
38
|
+
for j in range(len(coord)):
|
|
39
|
+
if i == j or element_list[j] != 'N':
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# Calculate distance between C and N
|
|
43
|
+
r_ij = np.linalg.norm(coord[i] - coord[j])
|
|
44
|
+
|
|
45
|
+
# Check if distance is close to a triple bond length
|
|
46
|
+
cn_triple_bond = triple_covalent_radii_lib('C') + triple_covalent_radii_lib('N')
|
|
47
|
+
|
|
48
|
+
if abs(r_ij - cn_triple_bond) < 0.3: # Within 0.3 bohr of ideal length
|
|
49
|
+
# Check if C is connected to only one other atom (besides N)
|
|
50
|
+
connections_to_c = 0
|
|
51
|
+
for k in range(len(coord)):
|
|
52
|
+
if k == i or k == j:
|
|
53
|
+
continue
|
|
54
|
+
|
|
55
|
+
r_ik = np.linalg.norm(coord[i] - coord[k])
|
|
56
|
+
cov_dist = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[k])
|
|
57
|
+
|
|
58
|
+
if r_ik < 1.3 * cov_dist: # Using 1.3 as a factor to account for bond length variations
|
|
59
|
+
connections_to_c += 1
|
|
60
|
+
|
|
61
|
+
# If C has only one other connection, it's likely a terminal cyano group
|
|
62
|
+
if connections_to_c <= 1:
|
|
63
|
+
cyano_atoms.append((i, j))
|
|
64
|
+
|
|
65
|
+
return cyano_atoms
|
|
66
|
+
|
|
67
|
+
def return_schlegel_const(self, element_1, element_2):
|
|
68
|
+
"""Return Schlegel's constant for a given element pair"""
|
|
69
|
+
if type(element_1) is int:
|
|
70
|
+
element_1 = number_element(element_1)
|
|
71
|
+
if type(element_2) is int:
|
|
72
|
+
element_2 = number_element(element_2)
|
|
73
|
+
|
|
74
|
+
parameter_B_matrix = [
|
|
75
|
+
[0.2573, 0.3401, 0.6937, 0.7126, 0.8335, 0.9491, 0.9491],
|
|
76
|
+
[0.3401, 0.9652, 1.2843, 1.4725, 1.6549, 1.7190, 1.7190],
|
|
77
|
+
[0.6937, 1.2843, 1.6925, 1.8238, 2.1164, 2.3185, 2.3185],
|
|
78
|
+
[0.7126, 1.4725, 1.8238, 2.0203, 2.2137, 2.5206, 2.5206],
|
|
79
|
+
[0.8335, 1.6549, 2.1164, 2.2137, 2.3718, 2.5110, 2.5110],
|
|
80
|
+
[0.9491, 1.7190, 2.3185, 2.5206, 2.5110, 2.5110, 2.5110],
|
|
81
|
+
[0.9491, 1.7190, 2.3185, 2.5206, 2.5110, 2.5110, 2.5110]
|
|
82
|
+
] # Bohr
|
|
83
|
+
|
|
84
|
+
first_period_table = ["H", "He"]
|
|
85
|
+
second_period_table = ["Li", "Be", "B", "C", "N", "O", "F", "Ne"]
|
|
86
|
+
third_period_table = ["Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar"]
|
|
87
|
+
fourth_period_table = ["K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br","Kr"]
|
|
88
|
+
fifth_period_table = ["Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc","Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te","I", "Xe"]
|
|
89
|
+
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"]
|
|
90
|
+
|
|
91
|
+
if element_1 in first_period_table:
|
|
92
|
+
idx_1 = 0
|
|
93
|
+
elif element_1 in second_period_table:
|
|
94
|
+
idx_1 = 1
|
|
95
|
+
elif element_1 in third_period_table:
|
|
96
|
+
idx_1 = 2
|
|
97
|
+
elif element_1 in fourth_period_table:
|
|
98
|
+
idx_1 = 3
|
|
99
|
+
elif element_1 in fifth_period_table:
|
|
100
|
+
idx_1 = 4
|
|
101
|
+
elif element_1 in sixth_period_table:
|
|
102
|
+
idx_1 = 5
|
|
103
|
+
else:
|
|
104
|
+
idx_1 = 6
|
|
105
|
+
|
|
106
|
+
if element_2 in first_period_table:
|
|
107
|
+
idx_2 = 0
|
|
108
|
+
elif element_2 in second_period_table:
|
|
109
|
+
idx_2 = 1
|
|
110
|
+
elif element_2 in third_period_table:
|
|
111
|
+
idx_2 = 2
|
|
112
|
+
elif element_2 in fourth_period_table:
|
|
113
|
+
idx_2 = 3
|
|
114
|
+
elif element_2 in fifth_period_table:
|
|
115
|
+
idx_2 = 4
|
|
116
|
+
elif element_2 in sixth_period_table:
|
|
117
|
+
idx_2 = 5
|
|
118
|
+
else:
|
|
119
|
+
idx_2 = 6
|
|
120
|
+
|
|
121
|
+
const_b = parameter_B_matrix[idx_1][idx_2]
|
|
122
|
+
return const_b
|
|
123
|
+
|
|
124
|
+
def d3_damping_function(self, r_ij, r0):
|
|
125
|
+
"""Calculate D3 rational damping function"""
|
|
126
|
+
a1 = self.d3_params.a1
|
|
127
|
+
a2 = self.d3_params.a2
|
|
128
|
+
|
|
129
|
+
# Rational damping function for C6 term
|
|
130
|
+
damp = 1.0 / (1.0 + 6.0 * (r_ij / (a1 * r0)) ** a2)
|
|
131
|
+
return damp
|
|
132
|
+
|
|
133
|
+
def get_d3_parameters(self, elem1, elem2):
|
|
134
|
+
"""Get D3 parameters for a pair of elements"""
|
|
135
|
+
# Get R4/R2 values
|
|
136
|
+
r4r2_1 = self.d3_params.get_r4r2(elem1)
|
|
137
|
+
r4r2_2 = self.d3_params.get_r4r2(elem2)
|
|
138
|
+
|
|
139
|
+
# C6 coefficients
|
|
140
|
+
c6_1 = r4r2_1 ** 2
|
|
141
|
+
c6_2 = r4r2_2 ** 2
|
|
142
|
+
c6 = np.sqrt(c6_1 * c6_2)
|
|
143
|
+
|
|
144
|
+
# C8 coefficients
|
|
145
|
+
c8 = 3.0 * c6 * np.sqrt(r4r2_1 * r4r2_2)
|
|
146
|
+
|
|
147
|
+
# r0 parameter (vdW radii)
|
|
148
|
+
r0 = np.sqrt(UFF_VDW_distance_lib(elem1) * UFF_VDW_distance_lib(elem2))
|
|
149
|
+
|
|
150
|
+
return c6, c8, r0
|
|
151
|
+
|
|
152
|
+
def calc_d3_correction(self, r_ij, elem1, elem2):
|
|
153
|
+
"""Calculate D3 dispersion correction to the force constant"""
|
|
154
|
+
# Get D3 parameters
|
|
155
|
+
c6, c8, r0 = self.get_d3_parameters(elem1, elem2)
|
|
156
|
+
|
|
157
|
+
# Damping functions
|
|
158
|
+
damp6 = self.d3_damping_function(r_ij, r0)
|
|
159
|
+
|
|
160
|
+
# D3 energy contribution (simplified)
|
|
161
|
+
e_disp = -self.d3_params.s6 * c6 / r_ij**6 * damp6
|
|
162
|
+
|
|
163
|
+
# Approximate second derivative (force constant)
|
|
164
|
+
fc_disp = self.d3_params.s6 * c6 * (42.0 / r_ij**8) * damp6
|
|
165
|
+
|
|
166
|
+
return fc_disp * 0.01 # Scale factor to match overall Hessian scale
|
|
167
|
+
|
|
168
|
+
def guess_schlegel_hessian(self, coord, element_list):
|
|
169
|
+
"""
|
|
170
|
+
Calculate approximate Hessian using Schlegel's approach augmented with D3 dispersion
|
|
171
|
+
and special handling for cyano groups
|
|
172
|
+
"""
|
|
173
|
+
# Detect cyano groups
|
|
174
|
+
cyano_atoms = self.detect_cyano_groups(coord, element_list)
|
|
175
|
+
cyano_set = set()
|
|
176
|
+
for c_idx, n_idx in cyano_atoms:
|
|
177
|
+
cyano_set.add(c_idx)
|
|
178
|
+
cyano_set.add(n_idx)
|
|
179
|
+
|
|
180
|
+
# Setup connectivity tables using BondConnectivity utility
|
|
181
|
+
BC = BondConnectivity()
|
|
182
|
+
b_c_mat = BC.bond_connect_matrix(element_list, coord)
|
|
183
|
+
connectivity_table = [BC.bond_connect_table(b_c_mat),
|
|
184
|
+
BC.angle_connect_table(b_c_mat),
|
|
185
|
+
BC.dihedral_angle_connect_table(b_c_mat)]
|
|
186
|
+
|
|
187
|
+
# Initialize RIC index list for all atom pairs
|
|
188
|
+
RIC_idx_list = [[i[0], i[1]] for i in itertools.combinations(range(len(coord)), 2)]
|
|
189
|
+
self.RIC_variable_num = len(RIC_idx_list)
|
|
190
|
+
RIC_approx_diag_hessian = [0.0] * self.RIC_variable_num
|
|
191
|
+
|
|
192
|
+
# Process connectivity table to build Hessian
|
|
193
|
+
for idx_list in connectivity_table:
|
|
194
|
+
for idx in idx_list:
|
|
195
|
+
# Bond stretching terms
|
|
196
|
+
if len(idx) == 2:
|
|
197
|
+
tmp_idx = sorted([idx[0], idx[1]])
|
|
198
|
+
distance = np.linalg.norm(coord[idx[0]] - coord[idx[1]])
|
|
199
|
+
|
|
200
|
+
elem_1 = element_list[idx[0]]
|
|
201
|
+
elem_2 = element_list[idx[1]]
|
|
202
|
+
const_b = self.return_schlegel_const(elem_1, elem_2)
|
|
203
|
+
tmpnum = RIC_idx_list.index(tmp_idx)
|
|
204
|
+
|
|
205
|
+
# Base Schlegel force constant
|
|
206
|
+
F = 1.734 / (distance - const_b) ** 3
|
|
207
|
+
|
|
208
|
+
# Check if this is a cyano bond
|
|
209
|
+
is_cyano_bond = False
|
|
210
|
+
for c_idx, n_idx in cyano_atoms:
|
|
211
|
+
if (idx[0] == c_idx and idx[1] == n_idx) or (idx[0] == n_idx and idx[1] == c_idx):
|
|
212
|
+
is_cyano_bond = True
|
|
213
|
+
break
|
|
214
|
+
|
|
215
|
+
# Add D3 dispersion contribution
|
|
216
|
+
d3_correction = self.calc_d3_correction(distance, elem_1, elem_2)
|
|
217
|
+
|
|
218
|
+
if is_cyano_bond:
|
|
219
|
+
# Enhanced force constant for C≡N triple bond
|
|
220
|
+
RIC_approx_diag_hessian[tmpnum] += self.cn_stretch_factor * F + d3_correction
|
|
221
|
+
else:
|
|
222
|
+
RIC_approx_diag_hessian[tmpnum] += F + d3_correction
|
|
223
|
+
|
|
224
|
+
# Angle bending terms
|
|
225
|
+
elif len(idx) == 3:
|
|
226
|
+
tmp_idx_1 = sorted([idx[0], idx[1]])
|
|
227
|
+
tmp_idx_2 = sorted([idx[1], idx[2]])
|
|
228
|
+
elem_1 = element_list[idx[0]]
|
|
229
|
+
elem_2 = element_list[idx[1]]
|
|
230
|
+
elem_3 = element_list[idx[2]]
|
|
231
|
+
tmpnum_1 = RIC_idx_list.index(tmp_idx_1)
|
|
232
|
+
tmpnum_2 = RIC_idx_list.index(tmp_idx_2)
|
|
233
|
+
|
|
234
|
+
# Check if angle involves cyano group
|
|
235
|
+
is_cyano_angle = (idx[0] in cyano_set or idx[1] in cyano_set or idx[2] in cyano_set)
|
|
236
|
+
|
|
237
|
+
# Base Schlegel force constant
|
|
238
|
+
if elem_1 == "H" or elem_3 == "H":
|
|
239
|
+
F_angle = 0.160
|
|
240
|
+
else:
|
|
241
|
+
F_angle = 0.250
|
|
242
|
+
|
|
243
|
+
# Add D3 dispersion contribution
|
|
244
|
+
d3_r1 = np.linalg.norm(coord[idx[0]] - coord[idx[1]])
|
|
245
|
+
d3_r2 = np.linalg.norm(coord[idx[1]] - coord[idx[2]])
|
|
246
|
+
d3_correction_1 = self.calc_d3_correction(d3_r1, elem_1, elem_2) * 0.2
|
|
247
|
+
d3_correction_2 = self.calc_d3_correction(d3_r2, elem_2, elem_3) * 0.2
|
|
248
|
+
|
|
249
|
+
if is_cyano_angle:
|
|
250
|
+
# Enhanced angle force constants for angles involving C≡N
|
|
251
|
+
RIC_approx_diag_hessian[tmpnum_1] += self.cn_angle_factor * F_angle + d3_correction_1
|
|
252
|
+
RIC_approx_diag_hessian[tmpnum_2] += self.cn_angle_factor * F_angle + d3_correction_2
|
|
253
|
+
else:
|
|
254
|
+
RIC_approx_diag_hessian[tmpnum_1] += F_angle + d3_correction_1
|
|
255
|
+
RIC_approx_diag_hessian[tmpnum_2] += F_angle + d3_correction_2
|
|
256
|
+
|
|
257
|
+
# Torsion (dihedral) terms
|
|
258
|
+
elif len(idx) == 4:
|
|
259
|
+
tmp_idx_1 = sorted([idx[0], idx[1]])
|
|
260
|
+
tmp_idx_2 = sorted([idx[1], idx[2]])
|
|
261
|
+
tmp_idx_3 = sorted([idx[2], idx[3]])
|
|
262
|
+
elem_1 = element_list[idx[0]]
|
|
263
|
+
elem_2 = element_list[idx[1]]
|
|
264
|
+
elem_3 = element_list[idx[2]]
|
|
265
|
+
elem_4 = element_list[idx[3]]
|
|
266
|
+
distance = np.linalg.norm(coord[idx[1]] - coord[idx[2]])
|
|
267
|
+
bond_length = covalent_radii_lib(elem_2) + covalent_radii_lib(elem_3)
|
|
268
|
+
tmpnum_1 = RIC_idx_list.index(tmp_idx_1)
|
|
269
|
+
tmpnum_2 = RIC_idx_list.index(tmp_idx_2)
|
|
270
|
+
tmpnum_3 = RIC_idx_list.index(tmp_idx_3)
|
|
271
|
+
|
|
272
|
+
# Base Schlegel torsion force constant
|
|
273
|
+
F_torsion = 0.0023 - 0.07 * (distance - bond_length)
|
|
274
|
+
|
|
275
|
+
# Check if torsion involves cyano group
|
|
276
|
+
is_cyano_torsion = (idx[0] in cyano_set or idx[1] in cyano_set or
|
|
277
|
+
idx[2] in cyano_set or idx[3] in cyano_set)
|
|
278
|
+
|
|
279
|
+
# Add D3 dispersion contribution
|
|
280
|
+
d3_r1 = np.linalg.norm(coord[idx[0]] - coord[idx[1]])
|
|
281
|
+
d3_r2 = np.linalg.norm(coord[idx[1]] - coord[idx[2]])
|
|
282
|
+
d3_r3 = np.linalg.norm(coord[idx[2]] - coord[idx[3]])
|
|
283
|
+
d3_correction_1 = self.calc_d3_correction(d3_r1, elem_1, elem_2) * 0.05
|
|
284
|
+
d3_correction_2 = self.calc_d3_correction(d3_r2, elem_2, elem_3) * 0.05
|
|
285
|
+
d3_correction_3 = self.calc_d3_correction(d3_r3, elem_3, elem_4) * 0.05
|
|
286
|
+
|
|
287
|
+
if is_cyano_torsion:
|
|
288
|
+
# Reduced torsion force constants for torsions involving C≡N
|
|
289
|
+
RIC_approx_diag_hessian[tmpnum_1] += self.cn_torsion_factor * F_torsion + d3_correction_1
|
|
290
|
+
RIC_approx_diag_hessian[tmpnum_2] += self.cn_torsion_factor * F_torsion + d3_correction_2
|
|
291
|
+
RIC_approx_diag_hessian[tmpnum_3] += self.cn_torsion_factor * F_torsion + d3_correction_3
|
|
292
|
+
else:
|
|
293
|
+
RIC_approx_diag_hessian[tmpnum_1] += F_torsion + d3_correction_1
|
|
294
|
+
RIC_approx_diag_hessian[tmpnum_2] += F_torsion + d3_correction_2
|
|
295
|
+
RIC_approx_diag_hessian[tmpnum_3] += F_torsion + d3_correction_3
|
|
296
|
+
|
|
297
|
+
# Convert to numpy array
|
|
298
|
+
RIC_approx_hessian = np.diag(RIC_approx_diag_hessian).astype("float64")
|
|
299
|
+
return RIC_approx_hessian
|
|
300
|
+
|
|
301
|
+
def main(self, coord, element_list, cart_gradient):
|
|
302
|
+
"""Main method to calculate the approximate Hessian"""
|
|
303
|
+
print("Generating Schlegel's approximate Hessian with D3 dispersion correction...")
|
|
304
|
+
|
|
305
|
+
# Calculate B matrix for redundant internal coordinates
|
|
306
|
+
b_mat = RedundantInternalCoordinates().B_matrix(coord)
|
|
307
|
+
self.RIC_variable_num = len(b_mat)
|
|
308
|
+
|
|
309
|
+
# Calculate approximate Hessian in internal coordinates
|
|
310
|
+
int_approx_hess = self.guess_schlegel_hessian(coord, element_list)
|
|
311
|
+
|
|
312
|
+
# Convert to Cartesian coordinates
|
|
313
|
+
cart_hess = np.dot(b_mat.T, np.dot(int_approx_hess, b_mat))
|
|
314
|
+
|
|
315
|
+
# Handle NaN values
|
|
316
|
+
cart_hess = np.nan_to_num(cart_hess, nan=0.0)
|
|
317
|
+
|
|
318
|
+
# Project out translational and rotational modes
|
|
319
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(cart_hess, element_list, coord)
|
|
320
|
+
|
|
321
|
+
return hess_proj
|
|
322
|
+
|