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,633 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from multioptpy.Parameters.parameter import UnitValueLib
|
|
4
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
5
|
+
from multioptpy.Parameters.parameter import GFN0Parameters
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class GFN0XTBApproxHessian:
|
|
9
|
+
"""GFN0-xTB approximate Hessian with special handling for cyano groups"""
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
12
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
13
|
+
self.params = GFN0Parameters()
|
|
14
|
+
self.bond_factor = 1.3 # Bond detection threshold factor
|
|
15
|
+
|
|
16
|
+
def detect_bond_connectivity(self, coord, element_list):
|
|
17
|
+
"""Calculate bond connectivity matrix and related data"""
|
|
18
|
+
n_atoms = len(coord)
|
|
19
|
+
dist_mat = np.zeros((n_atoms, n_atoms))
|
|
20
|
+
|
|
21
|
+
for i in range(n_atoms):
|
|
22
|
+
for j in range(i+1, n_atoms):
|
|
23
|
+
dist = np.linalg.norm(coord[i] - coord[j])
|
|
24
|
+
dist_mat[i, j] = dist_mat[j, i] = dist
|
|
25
|
+
|
|
26
|
+
# Bond connectivity based on covalent radii
|
|
27
|
+
bond_mat = np.zeros((n_atoms, n_atoms), dtype=bool)
|
|
28
|
+
|
|
29
|
+
for i in range(n_atoms):
|
|
30
|
+
for j in range(i+1, n_atoms):
|
|
31
|
+
r_i = self.params.get_radius(element_list[i])
|
|
32
|
+
r_j = self.params.get_radius(element_list[j])
|
|
33
|
+
|
|
34
|
+
# Use covalent radii to determine bonding
|
|
35
|
+
r_cov = r_i + r_j
|
|
36
|
+
|
|
37
|
+
if dist_mat[i, j] < r_cov * self.bond_factor:
|
|
38
|
+
bond_mat[i, j] = bond_mat[j, i] = True
|
|
39
|
+
|
|
40
|
+
return bond_mat, dist_mat
|
|
41
|
+
|
|
42
|
+
def analyze_molecular_structure(self, coord, element_list):
|
|
43
|
+
"""
|
|
44
|
+
Analyze molecular structure to identify bond types, cyano groups, and hybridization
|
|
45
|
+
|
|
46
|
+
Parameters:
|
|
47
|
+
coord: atomic coordinates (Bohr)
|
|
48
|
+
element_list: list of element symbols
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
topology: dictionary with molecular structure information
|
|
52
|
+
"""
|
|
53
|
+
# Get bond connectivity
|
|
54
|
+
bond_mat, dist_mat = self.detect_bond_connectivity(coord, element_list)
|
|
55
|
+
|
|
56
|
+
# Build list of bonds
|
|
57
|
+
n_atoms = len(coord)
|
|
58
|
+
bonds = []
|
|
59
|
+
|
|
60
|
+
for i in range(n_atoms):
|
|
61
|
+
for j in range(i+1, n_atoms):
|
|
62
|
+
if bond_mat[i, j]:
|
|
63
|
+
bonds.append((i, j))
|
|
64
|
+
|
|
65
|
+
# Count neighbors for each atom
|
|
66
|
+
neighbor_counts = np.sum(bond_mat, axis=1)
|
|
67
|
+
|
|
68
|
+
# Determine hybridization based on neighbor count and element type
|
|
69
|
+
hybridization = {}
|
|
70
|
+
|
|
71
|
+
for i in range(n_atoms):
|
|
72
|
+
elem = element_list[i]
|
|
73
|
+
n_neighbors = neighbor_counts[i]
|
|
74
|
+
|
|
75
|
+
if elem == 'C':
|
|
76
|
+
if n_neighbors == 4:
|
|
77
|
+
hybridization[i] = 'sp3'
|
|
78
|
+
elif n_neighbors == 3:
|
|
79
|
+
hybridization[i] = 'sp2'
|
|
80
|
+
elif n_neighbors == 2:
|
|
81
|
+
# Check angle to decide between sp and sp2
|
|
82
|
+
neighbors = [j for j in range(n_atoms) if bond_mat[i, j]]
|
|
83
|
+
if len(neighbors) == 2:
|
|
84
|
+
v1 = coord[neighbors[0]] - coord[i]
|
|
85
|
+
v2 = coord[neighbors[1]] - coord[i]
|
|
86
|
+
cos_angle = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
|
|
87
|
+
cos_angle = np.clip(cos_angle, -1.0, 1.0)
|
|
88
|
+
angle = np.arccos(cos_angle) * 180 / np.pi
|
|
89
|
+
hybridization[i] = 'sp' if angle > 160 else 'sp2'
|
|
90
|
+
else:
|
|
91
|
+
hybridization[i] = 'sp2' # Default
|
|
92
|
+
else:
|
|
93
|
+
hybridization[i] = 'sp3' # Default
|
|
94
|
+
|
|
95
|
+
elif elem == 'N':
|
|
96
|
+
if n_neighbors == 3:
|
|
97
|
+
hybridization[i] = 'sp2'
|
|
98
|
+
elif n_neighbors == 2:
|
|
99
|
+
hybridization[i] = 'sp2' # Most common
|
|
100
|
+
elif n_neighbors == 1:
|
|
101
|
+
# Check bond length to determine if it's a triple bond (CN)
|
|
102
|
+
neighbors = [j for j in range(n_atoms) if bond_mat[i, j]]
|
|
103
|
+
if len(neighbors) == 1 and element_list[neighbors[0]] == 'C':
|
|
104
|
+
bond_length = dist_mat[i, neighbors[0]]
|
|
105
|
+
ref_cn_triple = self.params.get_bond_length('C', 'N', 'triple') / self.bohr2angstroms
|
|
106
|
+
if abs(bond_length - ref_cn_triple) < 0.15:
|
|
107
|
+
hybridization[i] = 'sp'
|
|
108
|
+
else:
|
|
109
|
+
hybridization[i] = 'sp2'
|
|
110
|
+
else:
|
|
111
|
+
hybridization[i] = 'sp2'
|
|
112
|
+
else:
|
|
113
|
+
hybridization[i] = 'sp3'
|
|
114
|
+
|
|
115
|
+
elif elem == 'O':
|
|
116
|
+
if n_neighbors == 1:
|
|
117
|
+
# Check if it's a carbonyl
|
|
118
|
+
neighbors = [j for j in range(n_atoms) if bond_mat[i, j]]
|
|
119
|
+
if element_list[neighbors[0]] == 'C':
|
|
120
|
+
bond_length = dist_mat[i, neighbors[0]]
|
|
121
|
+
ref_co_double = self.params.get_bond_length('C', 'O', 'double') / self.bohr2angstroms
|
|
122
|
+
if abs(bond_length - ref_co_double) < 0.15:
|
|
123
|
+
hybridization[i] = 'sp2'
|
|
124
|
+
else:
|
|
125
|
+
hybridization[i] = 'sp3'
|
|
126
|
+
else:
|
|
127
|
+
hybridization[i] = 'sp3'
|
|
128
|
+
else:
|
|
129
|
+
hybridization[i] = 'sp3'
|
|
130
|
+
|
|
131
|
+
else:
|
|
132
|
+
# Default hybridization for other elements
|
|
133
|
+
hybridization[i] = 'sp3'
|
|
134
|
+
|
|
135
|
+
# Identify bond types
|
|
136
|
+
bond_types = {}
|
|
137
|
+
|
|
138
|
+
for i, j in bonds:
|
|
139
|
+
# Determine bond type based on elements and hybridization
|
|
140
|
+
hyb_i = hybridization.get(i, 'sp3')
|
|
141
|
+
hyb_j = hybridization.get(j, 'sp3')
|
|
142
|
+
|
|
143
|
+
# Default is single bond
|
|
144
|
+
bond_type = 'single'
|
|
145
|
+
|
|
146
|
+
# Special case: CN triple bond
|
|
147
|
+
if ((element_list[i] == 'C' and element_list[j] == 'N') or
|
|
148
|
+
(element_list[i] == 'N' and element_list[j] == 'C')):
|
|
149
|
+
if ((hyb_i == 'sp' and hyb_j == 'sp') or
|
|
150
|
+
(hyb_i == 'sp' and neighbor_counts[i] == 2 and neighbor_counts[j] == 1) or
|
|
151
|
+
(hyb_j == 'sp' and neighbor_counts[j] == 2 and neighbor_counts[i] == 1)):
|
|
152
|
+
# This looks like a cyano group
|
|
153
|
+
bond_type = 'triple'
|
|
154
|
+
|
|
155
|
+
# Carbon-carbon bonds
|
|
156
|
+
elif element_list[i] == 'C' and element_list[j] == 'C':
|
|
157
|
+
if hyb_i == 'sp' and hyb_j == 'sp':
|
|
158
|
+
bond_type = 'triple'
|
|
159
|
+
elif hyb_i == 'sp2' and hyb_j == 'sp2':
|
|
160
|
+
# Could be double bond or aromatic
|
|
161
|
+
bond_type = 'double' # Simplified
|
|
162
|
+
|
|
163
|
+
# Carbon-oxygen bonds
|
|
164
|
+
elif ((element_list[i] == 'C' and element_list[j] == 'O') or
|
|
165
|
+
(element_list[i] == 'O' and element_list[j] == 'C')):
|
|
166
|
+
if ((hyb_i == 'sp2' and hyb_j == 'sp2') or
|
|
167
|
+
(hyb_i == 'sp2' and neighbor_counts[j] == 1) or
|
|
168
|
+
(hyb_j == 'sp2' and neighbor_counts[i] == 1)):
|
|
169
|
+
# Carbonyl group
|
|
170
|
+
bond_type = 'double'
|
|
171
|
+
|
|
172
|
+
bond_types[(i, j)] = bond_types[(j, i)] = bond_type
|
|
173
|
+
|
|
174
|
+
# Identify cyano groups
|
|
175
|
+
cyano_groups = []
|
|
176
|
+
|
|
177
|
+
for i in range(n_atoms):
|
|
178
|
+
if element_list[i] == 'C' and hybridization.get(i, '') == 'sp':
|
|
179
|
+
n_partners = [j for j in range(n_atoms) if bond_mat[i, j] and element_list[j] == 'N']
|
|
180
|
+
other_partners = [j for j in range(n_atoms) if bond_mat[i, j] and element_list[j] != 'N']
|
|
181
|
+
|
|
182
|
+
if n_partners and len(other_partners) == 1:
|
|
183
|
+
n_idx = n_partners[0]
|
|
184
|
+
if bond_types.get((i, n_idx)) == 'triple':
|
|
185
|
+
cyano_groups.append((i, n_idx))
|
|
186
|
+
|
|
187
|
+
# Build lists of angles and dihedrals
|
|
188
|
+
angles = []
|
|
189
|
+
dihedrals = []
|
|
190
|
+
|
|
191
|
+
# Extract angles
|
|
192
|
+
for j in range(n_atoms):
|
|
193
|
+
bonded_to_j = [i for i in range(n_atoms) if bond_mat[i, j] and i != j]
|
|
194
|
+
for i in bonded_to_j:
|
|
195
|
+
for k in bonded_to_j:
|
|
196
|
+
if i < k: # Avoid duplicates
|
|
197
|
+
angles.append((i, j, k))
|
|
198
|
+
|
|
199
|
+
# Extract dihedrals
|
|
200
|
+
for j, k in bonds:
|
|
201
|
+
bonded_to_j = [i for i in range(n_atoms) if bond_mat[i, j] and i != j and i != k]
|
|
202
|
+
bonded_to_k = [l for l in range(n_atoms) if bond_mat[k, l] and l != k and l != j]
|
|
203
|
+
|
|
204
|
+
for i in bonded_to_j:
|
|
205
|
+
for l in bonded_to_k:
|
|
206
|
+
if i != l: # Avoid improper dihedrals here
|
|
207
|
+
dihedrals.append((i, j, k, l))
|
|
208
|
+
|
|
209
|
+
# Collect structure information
|
|
210
|
+
topology = {
|
|
211
|
+
'bonds': bonds,
|
|
212
|
+
'bond_types': bond_types,
|
|
213
|
+
'angles': angles,
|
|
214
|
+
'dihedrals': dihedrals,
|
|
215
|
+
'bond_mat': bond_mat,
|
|
216
|
+
'dist_mat': dist_mat,
|
|
217
|
+
'hybridization': hybridization,
|
|
218
|
+
'cyano_groups': cyano_groups,
|
|
219
|
+
'neighbor_counts': neighbor_counts
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return topology
|
|
223
|
+
|
|
224
|
+
def compute_partial_charges(self, coord, element_list, topology):
|
|
225
|
+
"""
|
|
226
|
+
Calculate partial charges using GFN0-xTB electronegativity equilibration
|
|
227
|
+
|
|
228
|
+
Parameters:
|
|
229
|
+
coord: atomic coordinates (Bohr)
|
|
230
|
+
element_list: list of element symbols
|
|
231
|
+
topology: molecular structure information
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
charges: array of partial charges
|
|
235
|
+
"""
|
|
236
|
+
n_atoms = len(coord)
|
|
237
|
+
bond_mat = topology['bond_mat']
|
|
238
|
+
dist_mat = topology['dist_mat']
|
|
239
|
+
|
|
240
|
+
# Initialize charges
|
|
241
|
+
charges = np.zeros(n_atoms)
|
|
242
|
+
|
|
243
|
+
# Step 1: Initial charge distribution based on electronegativity difference
|
|
244
|
+
for i, j in topology['bonds']:
|
|
245
|
+
en_i = self.params.get_en(element_list[i])
|
|
246
|
+
en_j = self.params.get_en(element_list[j])
|
|
247
|
+
|
|
248
|
+
# EN difference determines charge flow direction
|
|
249
|
+
en_diff = en_j - en_i
|
|
250
|
+
|
|
251
|
+
# Basic charge transfer based on EN difference
|
|
252
|
+
transfer = 0.05 * np.tanh(0.2 * en_diff)
|
|
253
|
+
|
|
254
|
+
# Apply charge transfer
|
|
255
|
+
charges[i] += transfer
|
|
256
|
+
charges[j] -= transfer
|
|
257
|
+
|
|
258
|
+
# Step 2: Special treatment for cyano groups
|
|
259
|
+
for c_idx, n_idx in topology['cyano_groups']:
|
|
260
|
+
# Cyano groups have strong polarization
|
|
261
|
+
charges[n_idx] -= 0.3 # Negative charge on N
|
|
262
|
+
charges[c_idx] += 0.3 # Positive charge on C
|
|
263
|
+
|
|
264
|
+
# Step 3: Normalize charges to ensure neutrality
|
|
265
|
+
charges -= np.mean(charges)
|
|
266
|
+
|
|
267
|
+
return charges
|
|
268
|
+
|
|
269
|
+
def gfn0_bond_hessian(self, coord, element_list, topology):
|
|
270
|
+
"""
|
|
271
|
+
Calculate bond stretching contributions to the Hessian
|
|
272
|
+
|
|
273
|
+
Parameters:
|
|
274
|
+
coord: atomic coordinates (Bohr)
|
|
275
|
+
element_list: list of element symbols
|
|
276
|
+
topology: molecular structure information
|
|
277
|
+
"""
|
|
278
|
+
bonds = topology['bonds']
|
|
279
|
+
bond_types = topology['bond_types']
|
|
280
|
+
|
|
281
|
+
for i, j in bonds:
|
|
282
|
+
r_vec = coord[j] - coord[i]
|
|
283
|
+
r_ij = np.linalg.norm(r_vec)
|
|
284
|
+
|
|
285
|
+
# Get bond type
|
|
286
|
+
bond_type = bond_types.get((i, j), 'single')
|
|
287
|
+
|
|
288
|
+
# Get force constant
|
|
289
|
+
force_const = self.params.get_bond_force_constant(
|
|
290
|
+
element_list[i], element_list[j], bond_type)
|
|
291
|
+
|
|
292
|
+
# Get reference bond length (convert to Bohr)
|
|
293
|
+
r0 = self.params.get_bond_length(
|
|
294
|
+
element_list[i], element_list[j], bond_type) / self.bohr2angstroms
|
|
295
|
+
|
|
296
|
+
# Calculate unit vector and projection operator
|
|
297
|
+
if r_ij > 1e-10:
|
|
298
|
+
u_ij = r_vec / r_ij
|
|
299
|
+
proj_op = np.outer(u_ij, u_ij)
|
|
300
|
+
else:
|
|
301
|
+
# Avoid division by zero
|
|
302
|
+
proj_op = np.eye(3) / 3.0
|
|
303
|
+
|
|
304
|
+
# Add to Cartesian Hessian
|
|
305
|
+
h_diag = force_const * proj_op
|
|
306
|
+
|
|
307
|
+
for n in range(3):
|
|
308
|
+
for m in range(3):
|
|
309
|
+
self.cart_hess[3*i+n, 3*i+m] += h_diag[n, m]
|
|
310
|
+
self.cart_hess[3*j+n, 3*j+m] += h_diag[n, m]
|
|
311
|
+
self.cart_hess[3*i+n, 3*j+m] -= h_diag[n, m]
|
|
312
|
+
self.cart_hess[3*j+n, 3*i+m] -= h_diag[n, m]
|
|
313
|
+
|
|
314
|
+
def gfn0_angle_hessian(self, coord, element_list, topology):
|
|
315
|
+
"""
|
|
316
|
+
Calculate angle bending contributions to the Hessian
|
|
317
|
+
|
|
318
|
+
Parameters:
|
|
319
|
+
coord: atomic coordinates (Bohr)
|
|
320
|
+
element_list: list of element symbols
|
|
321
|
+
topology: molecular structure information
|
|
322
|
+
"""
|
|
323
|
+
angles = topology['angles']
|
|
324
|
+
hybridization = topology['hybridization']
|
|
325
|
+
cyano_groups = topology['cyano_groups']
|
|
326
|
+
|
|
327
|
+
# Create lookup for cyano groups
|
|
328
|
+
cyano_carbons = [c for c, _ in cyano_groups]
|
|
329
|
+
cyano_nitrogens = [n for _, n in cyano_groups]
|
|
330
|
+
|
|
331
|
+
for i, j, k in angles:
|
|
332
|
+
# Calculate vectors and distances
|
|
333
|
+
r_ji = coord[i] - coord[j]
|
|
334
|
+
r_jk = coord[k] - coord[j]
|
|
335
|
+
r_ji_len = np.linalg.norm(r_ji)
|
|
336
|
+
r_jk_len = np.linalg.norm(r_jk)
|
|
337
|
+
|
|
338
|
+
# Skip if atoms are too close
|
|
339
|
+
if r_ji_len < 1e-10 or r_jk_len < 1e-10:
|
|
340
|
+
continue
|
|
341
|
+
|
|
342
|
+
# Special handling for X-C≡N angles
|
|
343
|
+
is_cyano_angle = False
|
|
344
|
+
|
|
345
|
+
if j in cyano_carbons:
|
|
346
|
+
n_idx = None
|
|
347
|
+
for c, n in cyano_groups:
|
|
348
|
+
if c == j:
|
|
349
|
+
n_idx = n
|
|
350
|
+
break
|
|
351
|
+
|
|
352
|
+
if n_idx is not None and (i == n_idx or k == n_idx):
|
|
353
|
+
is_cyano_angle = True
|
|
354
|
+
force_const = self.params.CNParams['kBend']
|
|
355
|
+
theta0 = np.pi # 180 degrees (linear)
|
|
356
|
+
|
|
357
|
+
# Regular angle if not cyano
|
|
358
|
+
if not is_cyano_angle:
|
|
359
|
+
# Get hybridization of central atom
|
|
360
|
+
hyb_j = hybridization.get(j, 'sp3')
|
|
361
|
+
|
|
362
|
+
# Get natural angle based on hybridization
|
|
363
|
+
theta0 = self.params.naturalAngles.get(hyb_j, self.params.naturalAngles['sp3'])
|
|
364
|
+
|
|
365
|
+
# Base force constant
|
|
366
|
+
force_const = self.params.kAngleBase
|
|
367
|
+
|
|
368
|
+
# Scale force constant based on central atom
|
|
369
|
+
if element_list[j] == 'C':
|
|
370
|
+
force_const *= 1.0
|
|
371
|
+
elif element_list[j] == 'N':
|
|
372
|
+
force_const *= 0.9
|
|
373
|
+
elif element_list[j] == 'O':
|
|
374
|
+
force_const *= 0.8
|
|
375
|
+
else:
|
|
376
|
+
force_const *= 0.7
|
|
377
|
+
|
|
378
|
+
# Calculate angle
|
|
379
|
+
cos_theta = np.dot(r_ji, r_jk) / (r_ji_len * r_jk_len)
|
|
380
|
+
cos_theta = np.clip(cos_theta, -0.999999, 0.999999) # Avoid numerical issues
|
|
381
|
+
theta = np.arccos(cos_theta)
|
|
382
|
+
|
|
383
|
+
# Calculate derivatives
|
|
384
|
+
sin_theta = np.sin(theta)
|
|
385
|
+
if sin_theta < 1e-10:
|
|
386
|
+
# Handle nearly linear case
|
|
387
|
+
continue
|
|
388
|
+
|
|
389
|
+
# Simplified derivatives for angle
|
|
390
|
+
d_i = (np.cross(np.cross(r_ji, r_jk), r_ji)) / (r_ji_len**2 * r_jk_len * sin_theta)
|
|
391
|
+
d_k = (np.cross(np.cross(r_jk, r_ji), r_jk)) / (r_ji_len * r_jk_len**2 * sin_theta)
|
|
392
|
+
d_j = -d_i - d_k
|
|
393
|
+
|
|
394
|
+
# Scale derivatives by force constant
|
|
395
|
+
d_i *= np.sqrt(force_const)
|
|
396
|
+
d_j *= np.sqrt(force_const)
|
|
397
|
+
d_k *= np.sqrt(force_const)
|
|
398
|
+
|
|
399
|
+
# Add to Hessian
|
|
400
|
+
for a in range(3):
|
|
401
|
+
for b in range(3):
|
|
402
|
+
# i-i block
|
|
403
|
+
self.cart_hess[3*i+a, 3*i+b] += d_i[a] * d_i[b]
|
|
404
|
+
|
|
405
|
+
# j-j block
|
|
406
|
+
self.cart_hess[3*j+a, 3*j+b] += d_j[a] * d_j[b]
|
|
407
|
+
|
|
408
|
+
# k-k block
|
|
409
|
+
self.cart_hess[3*k+a, 3*k+b] += d_k[a] * d_k[b]
|
|
410
|
+
|
|
411
|
+
# Cross terms
|
|
412
|
+
self.cart_hess[3*i+a, 3*j+b] += d_i[a] * d_j[b]
|
|
413
|
+
self.cart_hess[3*i+a, 3*k+b] += d_i[a] * d_k[b]
|
|
414
|
+
self.cart_hess[3*j+a, 3*i+b] += d_j[a] * d_i[b]
|
|
415
|
+
self.cart_hess[3*j+a, 3*k+b] += d_j[a] * d_k[b]
|
|
416
|
+
self.cart_hess[3*k+a, 3*i+b] += d_k[a] * d_i[b]
|
|
417
|
+
self.cart_hess[3*k+a, 3*j+b] += d_k[a] * d_j[b]
|
|
418
|
+
|
|
419
|
+
def gfn0_torsion_hessian(self, coord, element_list, topology):
|
|
420
|
+
"""
|
|
421
|
+
Calculate torsion (dihedral) contributions to the Hessian
|
|
422
|
+
|
|
423
|
+
Parameters:
|
|
424
|
+
coord: atomic coordinates (Bohr)
|
|
425
|
+
element_list: list of element symbols
|
|
426
|
+
topology: molecular structure information
|
|
427
|
+
"""
|
|
428
|
+
dihedrals = topology['dihedrals']
|
|
429
|
+
bond_types = topology['bond_types']
|
|
430
|
+
cyano_groups = topology['cyano_groups']
|
|
431
|
+
# Create lookup for cyano groups
|
|
432
|
+
cyano_bonds = set()
|
|
433
|
+
for c, n in cyano_groups:
|
|
434
|
+
cyano_bonds.add((c, n))
|
|
435
|
+
cyano_bonds.add((n, c))
|
|
436
|
+
|
|
437
|
+
for i, j, k, l in dihedrals:
|
|
438
|
+
# Calculate vectors along the bonds
|
|
439
|
+
r_ij = coord[j] - coord[i]
|
|
440
|
+
r_jk = coord[k] - coord[j]
|
|
441
|
+
r_kl = coord[l] - coord[k]
|
|
442
|
+
|
|
443
|
+
# Calculate cross products for the dihedral
|
|
444
|
+
n1 = np.cross(r_ij, r_jk)
|
|
445
|
+
n2 = np.cross(r_jk, r_kl)
|
|
446
|
+
|
|
447
|
+
# Skip if any of the cross products are too small
|
|
448
|
+
n1_norm = np.linalg.norm(n1)
|
|
449
|
+
n2_norm = np.linalg.norm(n2)
|
|
450
|
+
r_jk_norm = np.linalg.norm(r_jk)
|
|
451
|
+
|
|
452
|
+
if n1_norm < 1e-10 or n2_norm < 1e-10 or r_jk_norm < 1e-10:
|
|
453
|
+
continue
|
|
454
|
+
|
|
455
|
+
# Calculate the dihedral angle
|
|
456
|
+
cos_phi = np.dot(n1, n2) / (n1_norm * n2_norm)
|
|
457
|
+
cos_phi = np.clip(cos_phi, -0.999999, 0.999999) # Avoid numerical issues
|
|
458
|
+
|
|
459
|
+
sin_phi = np.dot(np.cross(n1, n2), r_jk) / (n1_norm * n2_norm * r_jk_norm)
|
|
460
|
+
phi = np.arctan2(sin_phi, cos_phi)
|
|
461
|
+
|
|
462
|
+
# Get torsion parameters
|
|
463
|
+
# Check if this is a cyano torsion
|
|
464
|
+
if (j, k) in cyano_bonds or (k, j) in cyano_bonds:
|
|
465
|
+
# Cyano group torsion - very small barrier
|
|
466
|
+
V2 = V3 = self.params.CNParams['kTorsion']
|
|
467
|
+
else:
|
|
468
|
+
# Normal torsion
|
|
469
|
+
bond_type = bond_types.get((j, k), 'single')
|
|
470
|
+
|
|
471
|
+
# Adjust based on bond type
|
|
472
|
+
if bond_type == 'triple':
|
|
473
|
+
# Triple bonds have near-zero torsion barriers
|
|
474
|
+
V2 = V3 = 0.001
|
|
475
|
+
elif bond_type == 'double':
|
|
476
|
+
# Double bonds have significant V2 (two-fold) term
|
|
477
|
+
V2 = self.params.V2Base * 2.0
|
|
478
|
+
V3 = self.params.V3Base * 0.5
|
|
479
|
+
elif bond_type == 'aromatic':
|
|
480
|
+
# Aromatic bonds have mixed character
|
|
481
|
+
V2 = self.params.V2Base * 1.5
|
|
482
|
+
V3 = self.params.V3Base
|
|
483
|
+
else:
|
|
484
|
+
# Single bonds have V3 (three-fold) term
|
|
485
|
+
V2 = self.params.V2Base * 0.5
|
|
486
|
+
V3 = self.params.V3Base * 1.5
|
|
487
|
+
|
|
488
|
+
# Calculate second derivatives
|
|
489
|
+
# V = V2/2 * (1-cos(2*phi)) + V3/2 * (1+cos(3*phi))
|
|
490
|
+
# For second derivatives, we need:
|
|
491
|
+
# d²V/dphi² = 2*V2*cos(2*phi) - 4.5*V3*cos(3*phi)
|
|
492
|
+
d2V = 2.0 * V2 * np.cos(2*phi) - 4.5 * V3 * np.cos(3*phi)
|
|
493
|
+
|
|
494
|
+
# Calculate derivatives of phi w.r.t. Cartesian coordinates
|
|
495
|
+
# (simplified approach)
|
|
496
|
+
# Calculate unit vectors
|
|
497
|
+
e_ij = r_ij / np.linalg.norm(r_ij) if np.linalg.norm(r_ij) > 1e-10 else np.zeros(3)
|
|
498
|
+
e_jk = r_jk / r_jk_norm
|
|
499
|
+
e_kl = r_kl / np.linalg.norm(r_kl) if np.linalg.norm(r_kl) > 1e-10 else np.zeros(3)
|
|
500
|
+
|
|
501
|
+
# Simplified derivatives calculation
|
|
502
|
+
n1_u = n1 / n1_norm if n1_norm > 1e-10 else np.zeros(3)
|
|
503
|
+
n2_u = n2 / n2_norm if n2_norm > 1e-10 else np.zeros(3)
|
|
504
|
+
|
|
505
|
+
# Calculate derivatives (this is a simplified approach)
|
|
506
|
+
# We calculate d(phi)/dr for each atom
|
|
507
|
+
g_i = np.cross(e_ij, n1_u) / (np.linalg.norm(r_ij) * sin_phi) if sin_phi > 1e-10 else np.zeros(3)
|
|
508
|
+
g_l = -np.cross(e_kl, n2_u) / (np.linalg.norm(r_kl) * sin_phi) if sin_phi > 1e-10 else np.zeros(3)
|
|
509
|
+
|
|
510
|
+
# Use conservation of angular momentum to get the middle terms
|
|
511
|
+
# These are simplified and not analytically perfect
|
|
512
|
+
g_j = -g_i - (r_jk_norm / np.linalg.norm(r_ij)) * g_i
|
|
513
|
+
g_k = -g_l - (r_jk_norm / np.linalg.norm(r_kl)) * g_l
|
|
514
|
+
|
|
515
|
+
# Scale derivatives by second derivative of potential
|
|
516
|
+
g_i *= np.sqrt(abs(d2V))
|
|
517
|
+
g_j *= np.sqrt(abs(d2V))
|
|
518
|
+
g_k *= np.sqrt(abs(d2V))
|
|
519
|
+
g_l *= np.sqrt(abs(d2V))
|
|
520
|
+
|
|
521
|
+
# Add to Hessian
|
|
522
|
+
atoms = [i, j, k, l]
|
|
523
|
+
derivatives = [g_i, g_j, g_k, g_l]
|
|
524
|
+
|
|
525
|
+
for a, g_a in enumerate(derivatives):
|
|
526
|
+
for b, g_b in enumerate(derivatives):
|
|
527
|
+
atom_a = atoms[a]
|
|
528
|
+
atom_b = atoms[b]
|
|
529
|
+
for x in range(3):
|
|
530
|
+
for y in range(3):
|
|
531
|
+
self.cart_hess[3*atom_a+x, 3*atom_b+y] += g_a[x] * g_b[y]
|
|
532
|
+
|
|
533
|
+
def gfn0_nonbonded_hessian(self, coord, element_list, topology):
|
|
534
|
+
"""
|
|
535
|
+
Calculate non-bonded interaction contributions to the Hessian
|
|
536
|
+
|
|
537
|
+
Parameters:
|
|
538
|
+
coord: atomic coordinates (Bohr)
|
|
539
|
+
element_list: list of element symbols
|
|
540
|
+
topology: molecular structure information
|
|
541
|
+
"""
|
|
542
|
+
n_atoms = len(coord)
|
|
543
|
+
bond_mat = topology['bond_mat']
|
|
544
|
+
charges = self.compute_partial_charges(coord, element_list, topology)
|
|
545
|
+
|
|
546
|
+
# Simplified non-bonded model for Hessian approximation
|
|
547
|
+
for i in range(n_atoms):
|
|
548
|
+
for j in range(i+1, n_atoms):
|
|
549
|
+
# Skip bonded atoms and 1-3 interactions
|
|
550
|
+
if bond_mat[i, j]:
|
|
551
|
+
continue
|
|
552
|
+
|
|
553
|
+
# Skip 1-3 interactions (atoms sharing a bonded neighbor)
|
|
554
|
+
has_common_neighbor = False
|
|
555
|
+
for k in range(n_atoms):
|
|
556
|
+
if bond_mat[i, k] and bond_mat[j, k]:
|
|
557
|
+
has_common_neighbor = True
|
|
558
|
+
break
|
|
559
|
+
|
|
560
|
+
if has_common_neighbor:
|
|
561
|
+
continue
|
|
562
|
+
|
|
563
|
+
# Calculate distance vector and magnitude
|
|
564
|
+
r_vec = coord[j] - coord[i]
|
|
565
|
+
r_ij = np.linalg.norm(r_vec)
|
|
566
|
+
|
|
567
|
+
if r_ij < 0.5: # Avoid too small distances
|
|
568
|
+
continue
|
|
569
|
+
|
|
570
|
+
# Calculate unit vector and projection operator
|
|
571
|
+
u_ij = r_vec / r_ij
|
|
572
|
+
proj_op = np.outer(u_ij, u_ij)
|
|
573
|
+
|
|
574
|
+
# Get atomic radii
|
|
575
|
+
r_i = self.params.get_radius(element_list[i])
|
|
576
|
+
r_j = self.params.get_radius(element_list[j])
|
|
577
|
+
|
|
578
|
+
# Simple repulsion term (r^-12)
|
|
579
|
+
rep_scale = 0.05 # Scale factor for repulsion
|
|
580
|
+
rep_sum = r_i + r_j
|
|
581
|
+
rep_term = rep_scale * ((rep_sum / r_ij)**12)
|
|
582
|
+
|
|
583
|
+
# Electrostatic term (q_i * q_j / r)
|
|
584
|
+
elec_scale = 0.1 # Scale factor for electrostatics
|
|
585
|
+
elec_term = elec_scale * charges[i] * charges[j] / r_ij
|
|
586
|
+
|
|
587
|
+
# Combine terms for total non-bonded Hessian contribution
|
|
588
|
+
hess_factor = (12.0 * rep_term / r_ij**2) + (2.0 * elec_term / r_ij**2)
|
|
589
|
+
|
|
590
|
+
# Add to Cartesian Hessian
|
|
591
|
+
for n in range(3):
|
|
592
|
+
for m in range(3):
|
|
593
|
+
self.cart_hess[3*i+n, 3*i+m] += hess_factor * proj_op[n, m]
|
|
594
|
+
self.cart_hess[3*j+n, 3*j+m] += hess_factor * proj_op[n, m]
|
|
595
|
+
self.cart_hess[3*i+n, 3*j+m] -= hess_factor * proj_op[n, m]
|
|
596
|
+
self.cart_hess[3*j+n, 3*i+m] -= hess_factor * proj_op[n, m]
|
|
597
|
+
|
|
598
|
+
def main(self, coord, element_list, cart_gradient):
|
|
599
|
+
"""
|
|
600
|
+
Calculate Hessian using GFN0-xTB model
|
|
601
|
+
|
|
602
|
+
Parameters:
|
|
603
|
+
coord: Atomic coordinates (N×3 array, Bohr)
|
|
604
|
+
element_list: List of element symbols
|
|
605
|
+
cart_gradient: Gradient in Cartesian coordinates
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
hess_proj: Hessian with rotational and translational modes projected out
|
|
609
|
+
"""
|
|
610
|
+
print("Generating Hessian using GFN0-xTB model...")
|
|
611
|
+
|
|
612
|
+
# Initialize Hessian matrix
|
|
613
|
+
n_atoms = len(coord)
|
|
614
|
+
self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
|
|
615
|
+
|
|
616
|
+
# Analyze molecular structure
|
|
617
|
+
topology = self.analyze_molecular_structure(coord, element_list)
|
|
618
|
+
|
|
619
|
+
# Calculate different Hessian components
|
|
620
|
+
self.gfn0_bond_hessian(coord, element_list, topology)
|
|
621
|
+
self.gfn0_angle_hessian(coord, element_list, topology)
|
|
622
|
+
self.gfn0_torsion_hessian(coord, element_list, topology)
|
|
623
|
+
self.gfn0_nonbonded_hessian(coord, element_list, topology)
|
|
624
|
+
|
|
625
|
+
# Symmetrize the Hessian matrix
|
|
626
|
+
for i in range(n_atoms*3):
|
|
627
|
+
for j in range(i):
|
|
628
|
+
self.cart_hess[j, i] = self.cart_hess[i, j]
|
|
629
|
+
|
|
630
|
+
# Project out rotational and translational modes
|
|
631
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
|
|
632
|
+
|
|
633
|
+
return hess_proj
|