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,709 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from collections import defaultdict
|
|
3
|
+
|
|
4
|
+
from multioptpy.Parameters.parameter import UnitValueLib
|
|
5
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
6
|
+
from multioptpy.Parameters.parameter import GFNFFParameters
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GFNFFApproxHessian:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
"""
|
|
12
|
+
GFNFF-based model Hessian implementation
|
|
13
|
+
Based on the GFN-FF method by Grimme et al., JCTC 2017, 13, 1989-2009
|
|
14
|
+
"""
|
|
15
|
+
self.bohr2angstroms = UnitValueLib().bohr2angstroms
|
|
16
|
+
self.hartree2kcalmol = UnitValueLib().hartree2kcalmol
|
|
17
|
+
self.params = GFNFFParameters()
|
|
18
|
+
self.bond_factor = 1.3 # Bond detection threshold factor
|
|
19
|
+
|
|
20
|
+
def estimate_atomic_charges(self, coord, element_list, bond_mat):
|
|
21
|
+
"""
|
|
22
|
+
Estimate atomic partial charges using a simplified EEQ model based on GFNFF approach
|
|
23
|
+
|
|
24
|
+
Parameters:
|
|
25
|
+
coord: atomic coordinates (Bohr)
|
|
26
|
+
element_list: list of element symbols
|
|
27
|
+
bond_mat: bond connectivity matrix
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
charges: array of estimated atomic charges
|
|
31
|
+
"""
|
|
32
|
+
n_atoms = len(coord)
|
|
33
|
+
charges = np.zeros(n_atoms)
|
|
34
|
+
|
|
35
|
+
# Calculate coordination numbers for charge dependence
|
|
36
|
+
cn = self.calculate_coordination_numbers(coord, element_list)
|
|
37
|
+
|
|
38
|
+
# Calculate electronegativity-based charge transfer
|
|
39
|
+
for i in range(n_atoms):
|
|
40
|
+
for j in range(n_atoms):
|
|
41
|
+
if i == j or not bond_mat[i, j]:
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
# Get electronegativity values
|
|
45
|
+
en_i = self.params.get_electronegativity(element_list[i])
|
|
46
|
+
en_j = self.params.get_electronegativity(element_list[j])
|
|
47
|
+
|
|
48
|
+
# Calculate distance in Angstroms
|
|
49
|
+
r_ij = np.linalg.norm(coord[i] - coord[j]) * self.bohr2angstroms
|
|
50
|
+
|
|
51
|
+
# CN-dependent electronegativity adjustment (simplified from GFNFF)
|
|
52
|
+
cn_ref_i = self.params.ref_cn.get(element_list[i], 1.0)
|
|
53
|
+
cn_ref_j = self.params.ref_cn.get(element_list[j], 1.0)
|
|
54
|
+
|
|
55
|
+
cn_factor_i = np.exp(-0.1 * (cn[i] - cn_ref_i)**2)
|
|
56
|
+
cn_factor_j = np.exp(-0.1 * (cn[j] - cn_ref_j)**2)
|
|
57
|
+
|
|
58
|
+
en_eff_i = en_i * cn_factor_i
|
|
59
|
+
en_eff_j = en_j * cn_factor_j
|
|
60
|
+
|
|
61
|
+
# Simple electronegativity-based charge transfer
|
|
62
|
+
charge_transfer = 0.1 * (en_eff_j - en_eff_i) / (r_ij * (en_eff_i + en_eff_j))
|
|
63
|
+
charges[i] += charge_transfer
|
|
64
|
+
charges[j] -= charge_transfer
|
|
65
|
+
|
|
66
|
+
# Normalize charges to ensure neutrality
|
|
67
|
+
charges -= np.mean(charges)
|
|
68
|
+
|
|
69
|
+
return charges
|
|
70
|
+
|
|
71
|
+
def calculate_coordination_numbers(self, coord, element_list):
|
|
72
|
+
"""
|
|
73
|
+
Calculate atomic coordination numbers using the counting function from GFNFF
|
|
74
|
+
|
|
75
|
+
Parameters:
|
|
76
|
+
coord: atomic coordinates (Bohr)
|
|
77
|
+
element_list: list of element symbols
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
cn: array of coordination numbers for each atom
|
|
81
|
+
"""
|
|
82
|
+
n_atoms = len(coord)
|
|
83
|
+
cn = np.zeros(n_atoms)
|
|
84
|
+
|
|
85
|
+
for i in range(n_atoms):
|
|
86
|
+
for j in range(n_atoms):
|
|
87
|
+
if i == j:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# Get covalent radii
|
|
91
|
+
r_cov_i = self.params.get_cov_radius(element_list[i])
|
|
92
|
+
r_cov_j = self.params.get_cov_radius(element_list[j])
|
|
93
|
+
|
|
94
|
+
# Calculate distance in Angstroms
|
|
95
|
+
dist_ij = np.linalg.norm(coord[i] - coord[j]) * self.bohr2angstroms
|
|
96
|
+
|
|
97
|
+
# Calculate coordination number contribution (GFNFF counting function)
|
|
98
|
+
r_cov = r_cov_i + r_cov_j
|
|
99
|
+
k_cn = 16.0 # Steepness of the counting function
|
|
100
|
+
cn_contrib = 1.0 / (1.0 + np.exp(-k_cn * (r_cov * 1.2 / dist_ij - 1.0)))
|
|
101
|
+
cn[i] += cn_contrib
|
|
102
|
+
|
|
103
|
+
return cn
|
|
104
|
+
|
|
105
|
+
def get_bond_connectivity(self, coord, element_list):
|
|
106
|
+
"""Calculate bond connectivity matrix and related data"""
|
|
107
|
+
n_atoms = len(coord)
|
|
108
|
+
dist_mat = np.zeros((n_atoms, n_atoms))
|
|
109
|
+
pair_cov_radii_mat = np.zeros((n_atoms, n_atoms))
|
|
110
|
+
|
|
111
|
+
for i in range(n_atoms):
|
|
112
|
+
for j in range(i+1, n_atoms):
|
|
113
|
+
dist = np.linalg.norm(coord[i] - coord[j])
|
|
114
|
+
dist_mat[i, j] = dist_mat[j, i] = dist
|
|
115
|
+
|
|
116
|
+
r_cov_i = self.params.get_cov_radius(element_list[i])
|
|
117
|
+
r_cov_j = self.params.get_cov_radius(element_list[j])
|
|
118
|
+
cov_sum = (r_cov_i + r_cov_j) / self.bohr2angstroms # Convert to bohr
|
|
119
|
+
|
|
120
|
+
pair_cov_radii_mat[i, j] = pair_cov_radii_mat[j, i] = cov_sum
|
|
121
|
+
|
|
122
|
+
# Bond connectivity matrix (True if bond exists between atoms)
|
|
123
|
+
bond_mat = dist_mat <= (pair_cov_radii_mat * self.bond_factor)
|
|
124
|
+
np.fill_diagonal(bond_mat, False) # No self-bonds
|
|
125
|
+
|
|
126
|
+
return bond_mat, dist_mat, pair_cov_radii_mat
|
|
127
|
+
|
|
128
|
+
def detect_hydrogen_bonds(self, coord, element_list, bond_mat, charges):
|
|
129
|
+
"""
|
|
130
|
+
Detect hydrogen bonds in the structure based on GFNFF criteria
|
|
131
|
+
|
|
132
|
+
Parameters:
|
|
133
|
+
coord: atomic coordinates (Bohr)
|
|
134
|
+
element_list: list of element symbols
|
|
135
|
+
bond_mat: bond connectivity matrix
|
|
136
|
+
charges: atomic partial charges
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
hbonds: list of hydrogen bonds as (donor, H, acceptor) triplets
|
|
140
|
+
"""
|
|
141
|
+
n_atoms = len(coord)
|
|
142
|
+
hbonds = []
|
|
143
|
+
|
|
144
|
+
# Define potential donors (electronegative elements that can have H attached)
|
|
145
|
+
donors = ['O', 'N', 'F', 'Cl', 'Br', 'I', 'S']
|
|
146
|
+
acceptors = ['O', 'N', 'F', 'Cl', 'Br', 'I', 'S']
|
|
147
|
+
|
|
148
|
+
# Find all hydrogen bonds
|
|
149
|
+
for i in range(n_atoms):
|
|
150
|
+
if element_list[i] != 'H':
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
# Find potential donor (atom bonded to H)
|
|
154
|
+
donor_idx = -1
|
|
155
|
+
for j in range(n_atoms):
|
|
156
|
+
if bond_mat[i, j] and element_list[j] in donors:
|
|
157
|
+
donor_idx = j
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
if donor_idx == -1:
|
|
161
|
+
continue # No suitable donor
|
|
162
|
+
|
|
163
|
+
donor_element = element_list[donor_idx]
|
|
164
|
+
|
|
165
|
+
# Look for acceptors
|
|
166
|
+
for acceptor_idx in range(n_atoms):
|
|
167
|
+
if acceptor_idx == donor_idx or bond_mat[i, acceptor_idx]:
|
|
168
|
+
continue # Skip donor and directly bonded atoms
|
|
169
|
+
|
|
170
|
+
acceptor_element = element_list[acceptor_idx]
|
|
171
|
+
if acceptor_element not in acceptors:
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
# Check if this acceptor-H-donor combination is valid
|
|
175
|
+
if (acceptor_element, 'H', donor_element) not in self.params.hbond_params and \
|
|
176
|
+
(donor_element, 'H', acceptor_element) not in self.params.hbond_params:
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
# Calculate H...acceptor distance
|
|
180
|
+
h_acc_dist = np.linalg.norm(coord[i] - coord[acceptor_idx]) * self.bohr2angstroms
|
|
181
|
+
|
|
182
|
+
# Calculate donor-H...acceptor angle
|
|
183
|
+
d_h_vec = coord[i] - coord[donor_idx]
|
|
184
|
+
h_acc_vec = coord[acceptor_idx] - coord[i]
|
|
185
|
+
d_h_len = np.linalg.norm(d_h_vec)
|
|
186
|
+
h_acc_len = np.linalg.norm(h_acc_vec)
|
|
187
|
+
|
|
188
|
+
if d_h_len > 0 and h_acc_len > 0:
|
|
189
|
+
cos_angle = np.dot(d_h_vec, h_acc_vec) / (d_h_len * h_acc_len)
|
|
190
|
+
cos_angle = np.clip(cos_angle, -1.0, 1.0)
|
|
191
|
+
angle = np.arccos(cos_angle) * 180.0 / np.pi
|
|
192
|
+
|
|
193
|
+
# Get parameters for this H-bond
|
|
194
|
+
h_params = self.params.get_hbond_params(donor_element, 'H', acceptor_element)
|
|
195
|
+
r0_hb = h_params[0] * self.bohr2angstroms # Convert to Angstroms
|
|
196
|
+
|
|
197
|
+
# Check hydrogen bond criteria: distance and angle
|
|
198
|
+
# Distance criterion: within 30% of optimal distance
|
|
199
|
+
# Angle criterion: > 120 degrees (approximately linear)
|
|
200
|
+
if h_acc_dist < 1.3 * r0_hb and angle > 120.0:
|
|
201
|
+
# Verify with partial charges (acceptor should be negative)
|
|
202
|
+
if charges[acceptor_idx] < -0.05:
|
|
203
|
+
hbonds.append((donor_idx, i, acceptor_idx))
|
|
204
|
+
|
|
205
|
+
return hbonds
|
|
206
|
+
|
|
207
|
+
def build_topology(self, coord, element_list):
|
|
208
|
+
"""
|
|
209
|
+
Build molecular topology including bonds, angles, dihedrals, and non-bonded interactions
|
|
210
|
+
|
|
211
|
+
Parameters:
|
|
212
|
+
coord: atomic coordinates (Bohr)
|
|
213
|
+
element_list: list of element symbols
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
topology: dictionary with molecular topology information
|
|
217
|
+
"""
|
|
218
|
+
# Get bond connectivity
|
|
219
|
+
bond_mat, dist_mat, cov_radii_mat = self.get_bond_connectivity(coord, element_list)
|
|
220
|
+
|
|
221
|
+
# Estimate atomic charges
|
|
222
|
+
charges = self.estimate_atomic_charges(coord, element_list, bond_mat)
|
|
223
|
+
|
|
224
|
+
# Detect hydrogen bonds
|
|
225
|
+
hbonds = self.detect_hydrogen_bonds(coord, element_list, bond_mat, charges)
|
|
226
|
+
|
|
227
|
+
# Calculate coordination numbers
|
|
228
|
+
cn = self.calculate_coordination_numbers(coord, element_list)
|
|
229
|
+
|
|
230
|
+
# Build lists of bonds, angles, and dihedrals
|
|
231
|
+
n_atoms = len(coord)
|
|
232
|
+
bonds = []
|
|
233
|
+
angles = []
|
|
234
|
+
dihedrals = []
|
|
235
|
+
|
|
236
|
+
# Extract bonds
|
|
237
|
+
for i in range(n_atoms):
|
|
238
|
+
for j in range(i+1, n_atoms):
|
|
239
|
+
if bond_mat[i, j]:
|
|
240
|
+
bonds.append((i, j))
|
|
241
|
+
|
|
242
|
+
# Extract angles
|
|
243
|
+
for j in range(n_atoms):
|
|
244
|
+
bonded_to_j = [i for i in range(n_atoms) if bond_mat[i, j] and i != j]
|
|
245
|
+
for i in bonded_to_j:
|
|
246
|
+
for k in bonded_to_j:
|
|
247
|
+
if i < k: # Avoid duplicates
|
|
248
|
+
angles.append((i, j, k))
|
|
249
|
+
|
|
250
|
+
# Extract dihedrals
|
|
251
|
+
for j, k in bonds:
|
|
252
|
+
bonded_to_j = [i for i in range(n_atoms) if bond_mat[i, j] and i != j and i != k]
|
|
253
|
+
bonded_to_k = [l for l in range(n_atoms) if bond_mat[k, l] and l != k and l != j]
|
|
254
|
+
|
|
255
|
+
for i in bonded_to_j:
|
|
256
|
+
for l in bonded_to_k:
|
|
257
|
+
if i != l: # Avoid improper dihedrals here
|
|
258
|
+
dihedrals.append((i, j, k, l))
|
|
259
|
+
|
|
260
|
+
# Build non-bonded pairs (atoms separated by at least 3 bonds)
|
|
261
|
+
nb_pairs = []
|
|
262
|
+
|
|
263
|
+
# Calculate shortest path lengths between atoms
|
|
264
|
+
bond_graph = defaultdict(list)
|
|
265
|
+
for i, j in bonds:
|
|
266
|
+
bond_graph[i].append(j)
|
|
267
|
+
bond_graph[j].append(i)
|
|
268
|
+
|
|
269
|
+
# For each atom pair, determine if they're separated by at least 3 bonds
|
|
270
|
+
for i in range(n_atoms):
|
|
271
|
+
for j in range(i+1, n_atoms):
|
|
272
|
+
if not bond_mat[i, j]: # Not directly bonded
|
|
273
|
+
# Check if they share a bonded neighbor (1-3 interaction)
|
|
274
|
+
common_neighbors = set(bond_graph[i]).intersection(bond_graph[j])
|
|
275
|
+
if not common_neighbors: # No common neighbors
|
|
276
|
+
nb_pairs.append((i, j))
|
|
277
|
+
else:
|
|
278
|
+
# Check if they're involved in a 1-4 interaction (part of a dihedral)
|
|
279
|
+
is_14 = False
|
|
280
|
+
for k in common_neighbors:
|
|
281
|
+
for l in bond_graph[j]:
|
|
282
|
+
if l != k and l in bond_graph[k] and l != i:
|
|
283
|
+
is_14 = True
|
|
284
|
+
break
|
|
285
|
+
if is_14:
|
|
286
|
+
break
|
|
287
|
+
|
|
288
|
+
if not is_14:
|
|
289
|
+
nb_pairs.append((i, j))
|
|
290
|
+
|
|
291
|
+
# Collect topology information
|
|
292
|
+
topology = {
|
|
293
|
+
'bonds': bonds,
|
|
294
|
+
'angles': angles,
|
|
295
|
+
'dihedrals': dihedrals,
|
|
296
|
+
'nb_pairs': nb_pairs,
|
|
297
|
+
'hbonds': hbonds,
|
|
298
|
+
'charges': charges,
|
|
299
|
+
'cn': cn,
|
|
300
|
+
'bond_mat': bond_mat,
|
|
301
|
+
'dist_mat': dist_mat
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return topology
|
|
305
|
+
|
|
306
|
+
def gfnff_bond_hessian(self, coord, element_list, topology):
|
|
307
|
+
"""
|
|
308
|
+
Calculate bond stretching contributions to the Hessian
|
|
309
|
+
|
|
310
|
+
Parameters:
|
|
311
|
+
coord: atomic coordinates (Bohr)
|
|
312
|
+
element_list: list of element symbols
|
|
313
|
+
topology: molecular topology information
|
|
314
|
+
"""
|
|
315
|
+
bonds = topology['bonds']
|
|
316
|
+
cn = topology['cn']
|
|
317
|
+
n_atoms = len(coord)
|
|
318
|
+
|
|
319
|
+
for i, j in bonds:
|
|
320
|
+
r_vec = coord[j] - coord[i]
|
|
321
|
+
r_ij = np.linalg.norm(r_vec)
|
|
322
|
+
|
|
323
|
+
# Get base bond parameters (r0 in bohr, k in atomic units)
|
|
324
|
+
r0, k_bond = self.params.get_bond_params(element_list[i], element_list[j])
|
|
325
|
+
|
|
326
|
+
# Apply CN-dependent scaling (as in GFNFF)
|
|
327
|
+
cn_ref_i = self.params.ref_cn.get(element_list[i], 1.0)
|
|
328
|
+
cn_ref_j = self.params.ref_cn.get(element_list[j], 1.0)
|
|
329
|
+
|
|
330
|
+
cn_factor = np.exp(-self.params.bond_decay * ((cn[i] - cn_ref_i)**2 + (cn[j] - cn_ref_j)**2))
|
|
331
|
+
force_const = k_bond * cn_factor * self.params.bond_scaling
|
|
332
|
+
|
|
333
|
+
# Calculate unit vector and projection operator
|
|
334
|
+
if r_ij > 1e-10:
|
|
335
|
+
u_ij = r_vec / r_ij
|
|
336
|
+
proj_op = np.outer(u_ij, u_ij)
|
|
337
|
+
else:
|
|
338
|
+
# Avoid division by zero
|
|
339
|
+
proj_op = np.eye(3) / 3.0
|
|
340
|
+
|
|
341
|
+
# Calculate force constant with exponential term for deviation from equilibrium
|
|
342
|
+
# (GFNFF uses a Morse-like function for bonds, but here we use a simpler approach)
|
|
343
|
+
exp_factor = np.exp(-2.0 * (r_ij - r0)**2)
|
|
344
|
+
force_const *= exp_factor
|
|
345
|
+
|
|
346
|
+
# Calculate Hessian blocks
|
|
347
|
+
h_diag = force_const * proj_op
|
|
348
|
+
|
|
349
|
+
# Add to Cartesian Hessian
|
|
350
|
+
for n in range(3):
|
|
351
|
+
for m in range(3):
|
|
352
|
+
self.cart_hess[3*i+n, 3*i+m] += h_diag[n, m]
|
|
353
|
+
self.cart_hess[3*j+n, 3*j+m] += h_diag[n, m]
|
|
354
|
+
self.cart_hess[3*i+n, 3*j+m] -= h_diag[n, m]
|
|
355
|
+
self.cart_hess[3*j+n, 3*i+m] -= h_diag[n, m]
|
|
356
|
+
|
|
357
|
+
def gfnff_angle_hessian(self, coord, element_list, topology):
|
|
358
|
+
"""
|
|
359
|
+
Calculate angle bending contributions to the Hessian
|
|
360
|
+
|
|
361
|
+
Parameters:
|
|
362
|
+
coord: atomic coordinates (Bohr)
|
|
363
|
+
element_list: list of element symbols
|
|
364
|
+
topology: molecular topology information
|
|
365
|
+
"""
|
|
366
|
+
angles = topology['angles']
|
|
367
|
+
cn = topology['cn']
|
|
368
|
+
n_atoms = len(coord)
|
|
369
|
+
|
|
370
|
+
for i, j, k in angles:
|
|
371
|
+
# Calculate vectors and distances
|
|
372
|
+
r_ji = coord[i] - coord[j]
|
|
373
|
+
r_jk = coord[k] - coord[j]
|
|
374
|
+
r_ji_len = np.linalg.norm(r_ji)
|
|
375
|
+
r_jk_len = np.linalg.norm(r_jk)
|
|
376
|
+
|
|
377
|
+
# Skip if atoms are too close
|
|
378
|
+
if r_ji_len < 1e-10 or r_jk_len < 1e-10:
|
|
379
|
+
continue
|
|
380
|
+
|
|
381
|
+
# Calculate angle
|
|
382
|
+
cos_theta = np.dot(r_ji, r_jk) / (r_ji_len * r_jk_len)
|
|
383
|
+
cos_theta = np.clip(cos_theta, -0.999999, 0.999999) # Avoid numerical issues
|
|
384
|
+
theta = np.arccos(cos_theta)
|
|
385
|
+
|
|
386
|
+
# Get angle parameters (theta0 in degrees, k in atomic units)
|
|
387
|
+
theta0, k_angle = self.params.get_angle_params(element_list[i], element_list[j], element_list[k])
|
|
388
|
+
theta0 = theta0 * np.pi / 180.0 # Convert to radians
|
|
389
|
+
|
|
390
|
+
# Apply coordination number scaling
|
|
391
|
+
cn_ref_j = self.params.ref_cn.get(element_list[j], 1.0)
|
|
392
|
+
cn_factor = np.exp(-0.1 * (cn[j] - cn_ref_j)**2)
|
|
393
|
+
force_const = k_angle * cn_factor
|
|
394
|
+
|
|
395
|
+
# Calculate unit vectors
|
|
396
|
+
u_ji = r_ji / r_ji_len
|
|
397
|
+
u_jk = r_jk / r_jk_len
|
|
398
|
+
|
|
399
|
+
# Calculate derivatives of the angle w.r.t. Cartesian coordinates
|
|
400
|
+
# This is a simplified approach for the Hessian
|
|
401
|
+
p_i = (u_ji - cos_theta * u_jk) / (r_ji_len * np.sin(theta))
|
|
402
|
+
p_k = (u_jk - cos_theta * u_ji) / (r_jk_len * np.sin(theta))
|
|
403
|
+
p_j = -p_i - p_k
|
|
404
|
+
|
|
405
|
+
# Build derivative vectors
|
|
406
|
+
deriv_i = p_i
|
|
407
|
+
deriv_j = p_j
|
|
408
|
+
deriv_k = p_k
|
|
409
|
+
|
|
410
|
+
# Calculate Hessian blocks (simplified approach)
|
|
411
|
+
for a in range(3):
|
|
412
|
+
for b in range(3):
|
|
413
|
+
# i-i block
|
|
414
|
+
self.cart_hess[3*i+a, 3*i+b] += force_const * deriv_i[a] * deriv_i[b]
|
|
415
|
+
|
|
416
|
+
# j-j block
|
|
417
|
+
self.cart_hess[3*j+a, 3*j+b] += force_const * deriv_j[a] * deriv_j[b]
|
|
418
|
+
|
|
419
|
+
# k-k block
|
|
420
|
+
self.cart_hess[3*k+a, 3*k+b] += force_const * deriv_k[a] * deriv_k[b]
|
|
421
|
+
|
|
422
|
+
# Cross terms
|
|
423
|
+
self.cart_hess[3*i+a, 3*j+b] += force_const * deriv_i[a] * deriv_j[b]
|
|
424
|
+
self.cart_hess[3*i+a, 3*k+b] += force_const * deriv_i[a] * deriv_k[b]
|
|
425
|
+
self.cart_hess[3*j+a, 3*i+b] += force_const * deriv_j[a] * deriv_i[b]
|
|
426
|
+
self.cart_hess[3*j+a, 3*k+b] += force_const * deriv_j[a] * deriv_k[b]
|
|
427
|
+
self.cart_hess[3*k+a, 3*i+b] += force_const * deriv_k[a] * deriv_i[b]
|
|
428
|
+
self.cart_hess[3*k+a, 3*j+b] += force_const * deriv_k[a] * deriv_j[b]
|
|
429
|
+
|
|
430
|
+
def gfnff_torsion_hessian(self, coord, element_list, topology):
|
|
431
|
+
"""
|
|
432
|
+
Calculate torsion (dihedral) contributions to the Hessian
|
|
433
|
+
|
|
434
|
+
Parameters:
|
|
435
|
+
coord: atomic coordinates (Bohr)
|
|
436
|
+
element_list: list of element symbols
|
|
437
|
+
topology: molecular topology information
|
|
438
|
+
"""
|
|
439
|
+
dihedrals = topology['dihedrals']
|
|
440
|
+
cn = topology['cn']
|
|
441
|
+
n_atoms = len(coord)
|
|
442
|
+
|
|
443
|
+
for i, j, k, l in dihedrals:
|
|
444
|
+
# Calculate vectors along the bonds
|
|
445
|
+
r_ij = coord[j] - coord[i]
|
|
446
|
+
r_jk = coord[k] - coord[j]
|
|
447
|
+
r_kl = coord[l] - coord[k]
|
|
448
|
+
|
|
449
|
+
# Calculate cross products for the dihedral
|
|
450
|
+
n1 = np.cross(r_ij, r_jk)
|
|
451
|
+
n2 = np.cross(r_jk, r_kl)
|
|
452
|
+
|
|
453
|
+
# Skip if any of the cross products are too small
|
|
454
|
+
n1_norm = np.linalg.norm(n1)
|
|
455
|
+
n2_norm = np.linalg.norm(n2)
|
|
456
|
+
r_jk_norm = np.linalg.norm(r_jk)
|
|
457
|
+
|
|
458
|
+
if n1_norm < 1e-10 or n2_norm < 1e-10 or r_jk_norm < 1e-10:
|
|
459
|
+
continue
|
|
460
|
+
|
|
461
|
+
# Calculate the dihedral angle
|
|
462
|
+
cos_phi = np.dot(n1, n2) / (n1_norm * n2_norm)
|
|
463
|
+
cos_phi = np.clip(cos_phi, -0.999999, 0.999999) # Avoid numerical issues
|
|
464
|
+
|
|
465
|
+
sin_phi = np.dot(np.cross(n1, n2), r_jk) / (n1_norm * n2_norm * r_jk_norm)
|
|
466
|
+
phi = np.arctan2(sin_phi, cos_phi)
|
|
467
|
+
|
|
468
|
+
# Get torsion parameters (V1, V2, V3 in hartree)
|
|
469
|
+
v1, v2, v3 = self.params.get_torsion_params(
|
|
470
|
+
element_list[i], element_list[j], element_list[k], element_list[l]
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
# Apply coordination number scaling (simplified)
|
|
474
|
+
cn_ref_j = self.params.ref_cn.get(element_list[j], 1.0)
|
|
475
|
+
cn_ref_k = self.params.ref_cn.get(element_list[k], 1.0)
|
|
476
|
+
cn_factor = np.exp(-0.05 * ((cn[j] - cn_ref_j)**2 + (cn[k] - cn_ref_k)**2))
|
|
477
|
+
|
|
478
|
+
v1 *= cn_factor
|
|
479
|
+
v2 *= cn_factor
|
|
480
|
+
v3 *= cn_factor
|
|
481
|
+
|
|
482
|
+
# Calculate forces based on derivatives of the potential
|
|
483
|
+
# V = v1*(1+cos(phi)) + v2*(1+cos(2*phi)) + v3*(1+cos(3*phi-pi))
|
|
484
|
+
f1 = -v1 * np.sin(phi)
|
|
485
|
+
f2 = -2 * v2 * np.sin(2*phi)
|
|
486
|
+
f3 = -3 * v3 * np.sin(3*phi)
|
|
487
|
+
|
|
488
|
+
force = f1 + f2 + f3
|
|
489
|
+
|
|
490
|
+
# Calculate second derivatives (simplified for Hessian)
|
|
491
|
+
# This is a simplified approach; a complete implementation would
|
|
492
|
+
# include all second derivatives of the torsion angles
|
|
493
|
+
k2 = v1 * np.cos(phi) + 4 * v2 * np.cos(2*phi) + 9 * v3 * np.cos(3*phi)
|
|
494
|
+
|
|
495
|
+
# Calculate derivatives of phi w.r.t. Cartesian coordinates
|
|
496
|
+
# (Simplified approach - for a full treatment, see the properly derived formulas)
|
|
497
|
+
# This approximation might not be accurate for all geometries
|
|
498
|
+
|
|
499
|
+
# Normalize bond vectors
|
|
500
|
+
e_ij = r_ij / np.linalg.norm(r_ij)
|
|
501
|
+
e_jk = r_jk / r_jk_norm
|
|
502
|
+
e_kl = r_kl / np.linalg.norm(r_kl)
|
|
503
|
+
|
|
504
|
+
# Normalized cross products
|
|
505
|
+
n1 = n1 / n1_norm
|
|
506
|
+
n2 = n2 / n2_norm
|
|
507
|
+
|
|
508
|
+
# Calculate derivatives (simplified)
|
|
509
|
+
# For a complete treatment, derive the derivatives of the dihedral angle
|
|
510
|
+
# with respect to all atomic positions
|
|
511
|
+
|
|
512
|
+
# Apply a simple projection approach for the derivatives
|
|
513
|
+
# These are simplified derivatives and not analytically correct for all geometries
|
|
514
|
+
deriv_i = np.cross(e_ij, n1) / np.linalg.norm(r_ij)
|
|
515
|
+
deriv_l = np.cross(n2, e_kl) / np.linalg.norm(r_kl)
|
|
516
|
+
deriv_j = -deriv_i - np.cross(e_jk, n1) / np.linalg.norm(r_ij)
|
|
517
|
+
deriv_k = -deriv_l - np.cross(n2, e_jk) / np.linalg.norm(r_kl)
|
|
518
|
+
|
|
519
|
+
# Scale derivatives by force constant
|
|
520
|
+
deriv_i *= force
|
|
521
|
+
deriv_j *= force
|
|
522
|
+
deriv_k *= force
|
|
523
|
+
deriv_l *= force
|
|
524
|
+
|
|
525
|
+
# Add to Cartesian Hessian
|
|
526
|
+
# This is a very simplified Hessian contribution
|
|
527
|
+
atoms = [i, j, k, l]
|
|
528
|
+
derivs = [deriv_i, deriv_j, deriv_k, deriv_l]
|
|
529
|
+
|
|
530
|
+
for m in range(4):
|
|
531
|
+
for n in range(4):
|
|
532
|
+
if m <= n: # Only calculate upper triangular part
|
|
533
|
+
for a in range(3):
|
|
534
|
+
for b in range(3):
|
|
535
|
+
self.cart_hess[3*atoms[m]+a, 3*atoms[n]+b] += k2 * derivs[m][a] * derivs[n][b]
|
|
536
|
+
|
|
537
|
+
# Make Hessian symmetric
|
|
538
|
+
for m in range(3*n_atoms):
|
|
539
|
+
for n in range(m):
|
|
540
|
+
self.cart_hess[m, n] = self.cart_hess[n, m]
|
|
541
|
+
|
|
542
|
+
def gfnff_hbond_hessian(self, coord, element_list, topology):
|
|
543
|
+
"""
|
|
544
|
+
Calculate hydrogen bond contributions to the Hessian
|
|
545
|
+
|
|
546
|
+
Parameters:
|
|
547
|
+
coord: atomic coordinates (Bohr)
|
|
548
|
+
element_list: list of element symbols
|
|
549
|
+
topology: molecular topology information
|
|
550
|
+
"""
|
|
551
|
+
hbonds = topology['hbonds']
|
|
552
|
+
|
|
553
|
+
for donor_idx, h_idx, acceptor_idx in hbonds:
|
|
554
|
+
# Get atom elements
|
|
555
|
+
donor_element = element_list[donor_idx]
|
|
556
|
+
acceptor_element = element_list[acceptor_idx]
|
|
557
|
+
|
|
558
|
+
# Get H-bond parameters
|
|
559
|
+
r0_hb, k_hb = self.params.get_hbond_params(donor_element, 'H', acceptor_element)
|
|
560
|
+
|
|
561
|
+
# Calculate vectors and distances
|
|
562
|
+
r_dh = coord[h_idx] - coord[donor_idx]
|
|
563
|
+
r_ha = coord[acceptor_idx] - coord[h_idx]
|
|
564
|
+
r_dh_len = np.linalg.norm(r_dh)
|
|
565
|
+
r_ha_len = np.linalg.norm(r_ha)
|
|
566
|
+
|
|
567
|
+
# Skip if atoms are too close
|
|
568
|
+
if r_dh_len < 1e-10 or r_ha_len < 1e-10:
|
|
569
|
+
continue
|
|
570
|
+
|
|
571
|
+
# Calculate angle
|
|
572
|
+
cos_angle = np.dot(r_dh, r_ha) / (r_dh_len * r_ha_len)
|
|
573
|
+
cos_angle = np.clip(cos_angle, -0.999999, 0.999999) # Avoid numerical issues
|
|
574
|
+
angle = np.arccos(cos_angle)
|
|
575
|
+
|
|
576
|
+
# H-bond force constant depends on distance and angle
|
|
577
|
+
# Optimal H-bond is linear (180 degrees) and at equilibrium distance
|
|
578
|
+
|
|
579
|
+
# Distance-dependent term
|
|
580
|
+
dist_factor = np.exp(-(r_ha_len - r0_hb)**2 / (2.0 * 0.3**2)) # Gaussian shape
|
|
581
|
+
|
|
582
|
+
# Angle-dependent term (preference for linear H-bonds)
|
|
583
|
+
angle_factor = (1.0 + np.cos(angle - np.pi))**2 / 4.0 # Peaks at 180 degrees
|
|
584
|
+
|
|
585
|
+
# Combined force constant
|
|
586
|
+
force_const = k_hb * dist_factor * angle_factor
|
|
587
|
+
|
|
588
|
+
# Calculate unit vectors for H-A bond
|
|
589
|
+
u_ha = r_ha / r_ha_len
|
|
590
|
+
proj_op = np.outer(u_ha, u_ha)
|
|
591
|
+
|
|
592
|
+
# Calculate Hessian blocks for H-bond stretching
|
|
593
|
+
# This is a simplified approach focusing on the H-A distance
|
|
594
|
+
for n in range(3):
|
|
595
|
+
for m in range(3):
|
|
596
|
+
self.cart_hess[3*h_idx+n, 3*h_idx+m] += force_const * proj_op[n, m]
|
|
597
|
+
self.cart_hess[3*acceptor_idx+n, 3*acceptor_idx+m] += force_const * proj_op[n, m]
|
|
598
|
+
self.cart_hess[3*h_idx+n, 3*acceptor_idx+m] -= force_const * proj_op[n, m]
|
|
599
|
+
self.cart_hess[3*acceptor_idx+n, 3*h_idx+m] -= force_const * proj_op[n, m]
|
|
600
|
+
|
|
601
|
+
# Angle component is more complex and not included in this simplified model
|
|
602
|
+
|
|
603
|
+
def gfnff_nonbonded_hessian(self, coord, element_list, topology):
|
|
604
|
+
"""
|
|
605
|
+
Calculate non-bonded interactions (dispersion and repulsion) to the Hessian
|
|
606
|
+
|
|
607
|
+
Parameters:
|
|
608
|
+
coord: atomic coordinates (Bohr)
|
|
609
|
+
element_list: list of element symbols
|
|
610
|
+
topology: molecular topology information
|
|
611
|
+
"""
|
|
612
|
+
n_atoms = len(coord)
|
|
613
|
+
nb_pairs = topology['nb_pairs']
|
|
614
|
+
charges = topology['charges']
|
|
615
|
+
|
|
616
|
+
for i, j in nb_pairs:
|
|
617
|
+
# Calculate distance vector and magnitude
|
|
618
|
+
r_vec = coord[i] - coord[j]
|
|
619
|
+
r_ij = np.linalg.norm(r_vec)
|
|
620
|
+
|
|
621
|
+
# Skip if atoms are too close
|
|
622
|
+
if r_ij < 0.1:
|
|
623
|
+
continue
|
|
624
|
+
|
|
625
|
+
# Get element properties
|
|
626
|
+
alpha_i = self.params.get_polarizability(element_list[i])
|
|
627
|
+
alpha_j = self.params.get_polarizability(element_list[j])
|
|
628
|
+
|
|
629
|
+
# Calculate simple dispersion C6 coefficient (simplified DFT-D3 style)
|
|
630
|
+
c6_ij = 2.0 * alpha_i * alpha_j / (alpha_i/alpha_j + alpha_j/alpha_i) * 0.05
|
|
631
|
+
|
|
632
|
+
# Calculate van der Waals radii
|
|
633
|
+
vdw_i = self.params.get_vdw_radius(element_list[i]) / self.bohr2angstroms
|
|
634
|
+
vdw_j = self.params.get_vdw_radius(element_list[j]) / self.bohr2angstroms
|
|
635
|
+
vdw_sum = vdw_i + vdw_j
|
|
636
|
+
|
|
637
|
+
# Calculate dispersion energy and derivatives
|
|
638
|
+
# Repulsive term: simple exponential
|
|
639
|
+
rep_const = 0.3 # Repulsion strength
|
|
640
|
+
rep_energy = rep_const * np.exp(-(r_ij/vdw_sum - 0.6) * 12.0)
|
|
641
|
+
rep_deriv = -12.0 * rep_const * np.exp(-(r_ij/vdw_sum - 0.6) * 12.0) * (1.0/vdw_sum) / r_ij
|
|
642
|
+
|
|
643
|
+
# Attractive dispersion term (D3-like with BJ-damping)
|
|
644
|
+
r0_ij = 0.5 * vdw_sum
|
|
645
|
+
a1, a2 = 0.4, 3.0 # BJ-damping parameters
|
|
646
|
+
damp_factor = r_ij**6 / (r_ij**6 + (a1*r0_ij + a2)**6)
|
|
647
|
+
disp_energy = -self.params.d4_s6 * c6_ij * damp_factor / r_ij**6
|
|
648
|
+
|
|
649
|
+
# Compute the derivative of the damping factor
|
|
650
|
+
damp_deriv = 6 * r_ij**5 * (a1*r0_ij + a2)**6 / (r_ij**6 + (a1*r0_ij + a2)**6)**2
|
|
651
|
+
disp_deriv = self.params.d4_s6 * c6_ij * (6 * damp_factor / r_ij**7 - damp_deriv / r_ij**6)
|
|
652
|
+
|
|
653
|
+
# Total force
|
|
654
|
+
force = rep_deriv + disp_deriv
|
|
655
|
+
|
|
656
|
+
# Calculate unit vector and projection operator
|
|
657
|
+
u_ij = r_vec / r_ij
|
|
658
|
+
proj_op = np.outer(u_ij, u_ij)
|
|
659
|
+
|
|
660
|
+
# Simplified second derivatives for Hessian
|
|
661
|
+
# This is not analytically correct but provides an approximation
|
|
662
|
+
# For correct treatment, one would need the full second derivatives
|
|
663
|
+
hess_factor = (force / r_ij) + 0.2 # Adding a small positive constant for stability
|
|
664
|
+
|
|
665
|
+
# Add to Cartesian Hessian
|
|
666
|
+
for n in range(3):
|
|
667
|
+
for m in range(3):
|
|
668
|
+
self.cart_hess[3*i+n, 3*i+m] += hess_factor * proj_op[n, m]
|
|
669
|
+
self.cart_hess[3*j+n, 3*j+m] += hess_factor * proj_op[n, m]
|
|
670
|
+
self.cart_hess[3*i+n, 3*j+m] -= hess_factor * proj_op[n, m]
|
|
671
|
+
self.cart_hess[3*j+n, 3*i+m] -= hess_factor * proj_op[n, m]
|
|
672
|
+
|
|
673
|
+
def main(self, coord, element_list, cart_gradient):
|
|
674
|
+
"""
|
|
675
|
+
Calculate Hessian using GFNFF-based model
|
|
676
|
+
|
|
677
|
+
Parameters:
|
|
678
|
+
coord: Atomic coordinates (N×3 array, Bohr)
|
|
679
|
+
element_list: List of element symbols
|
|
680
|
+
cart_gradient: Gradient in Cartesian coordinates
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
hess_proj: Hessian with rotational and translational modes projected out
|
|
684
|
+
"""
|
|
685
|
+
print("Generating Hessian using GFNFF model...")
|
|
686
|
+
|
|
687
|
+
# Initialize Hessian matrix
|
|
688
|
+
n_atoms = len(coord)
|
|
689
|
+
self.cart_hess = np.zeros((n_atoms*3, n_atoms*3), dtype="float64")
|
|
690
|
+
|
|
691
|
+
# Build molecular topology
|
|
692
|
+
topology = self.build_topology(coord, element_list)
|
|
693
|
+
|
|
694
|
+
# Calculate different Hessian components
|
|
695
|
+
self.gfnff_bond_hessian(coord, element_list, topology)
|
|
696
|
+
self.gfnff_angle_hessian(coord, element_list, topology)
|
|
697
|
+
self.gfnff_torsion_hessian(coord, element_list, topology)
|
|
698
|
+
self.gfnff_hbond_hessian(coord, element_list, topology)
|
|
699
|
+
self.gfnff_nonbonded_hessian(coord, element_list, topology)
|
|
700
|
+
|
|
701
|
+
# Symmetrize the Hessian matrix
|
|
702
|
+
for i in range(n_atoms*3):
|
|
703
|
+
for j in range(i):
|
|
704
|
+
self.cart_hess[j, i] = self.cart_hess[i, j]
|
|
705
|
+
|
|
706
|
+
# Project out rotational and translational modes
|
|
707
|
+
hess_proj = Calculationtools().project_out_hess_tr_and_rot_for_coord(self.cart_hess, element_list, coord)
|
|
708
|
+
|
|
709
|
+
return hess_proj
|