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,884 @@
|
|
|
1
|
+
import itertools
|
|
2
|
+
import numpy as np
|
|
3
|
+
import copy
|
|
4
|
+
from scipy.spatial import cKDTree
|
|
5
|
+
from scipy.interpolate import make_interp_spline
|
|
6
|
+
|
|
7
|
+
from multioptpy.Interpolation.interpolation import spline_interpolation
|
|
8
|
+
from multioptpy.Potential.idpp import IDPP
|
|
9
|
+
from multioptpy.Parameters.parameter import covalent_radii_lib, atomic_mass, UnitValueLib
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import torch
|
|
13
|
+
except:
|
|
14
|
+
print("You cannot import pyTorch.")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CalculationStructInfo:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
def calculate_cos(self, bg, g):
|
|
22
|
+
if np.linalg.norm(bg) == 0.0 or np.linalg.norm(g) == 0.0:
|
|
23
|
+
cos = 2.0
|
|
24
|
+
else:
|
|
25
|
+
cos = np.sum(bg * g) / (np.linalg.norm(g) * np.linalg.norm(bg))
|
|
26
|
+
return cos
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def calculate_distance(self, atom1, atom2):
|
|
30
|
+
atom1, atom2 = np.array(atom1, dtype="float64"), np.array(atom2, dtype="float64")
|
|
31
|
+
distance = np.linalg.norm(atom2 - atom1)
|
|
32
|
+
return distance
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def calculate_bond_angle(self, atom1, atom2, atom3):
|
|
36
|
+
atom1, atom2, atom3 = np.array(atom1, dtype="float64"), np.array(atom2, dtype="float64"), np.array(atom3, dtype="float64")
|
|
37
|
+
vector1 = atom1 - atom2
|
|
38
|
+
vector2 = atom3 - atom2
|
|
39
|
+
|
|
40
|
+
cos_angle = np.dot(vector1, vector2) / (np.linalg.norm(vector1) * np.linalg.norm(vector2))
|
|
41
|
+
angle = np.arccos(cos_angle)
|
|
42
|
+
angle_deg = np.degrees(angle)
|
|
43
|
+
|
|
44
|
+
return angle_deg
|
|
45
|
+
|
|
46
|
+
def calculate_dihedral_angle(self, atom1, atom2, atom3, atom4):
|
|
47
|
+
atom1, atom2, atom3, atom4 = np.array(atom1, dtype="float64"), np.array(atom2, dtype="float64"), np.array(atom3, dtype="float64"), np.array(atom4, dtype="float64")
|
|
48
|
+
|
|
49
|
+
a1 = atom2 - atom1
|
|
50
|
+
a2 = atom3 - atom2
|
|
51
|
+
a3 = atom4 - atom3
|
|
52
|
+
|
|
53
|
+
v1 = np.cross(a1, a2)
|
|
54
|
+
v1 = v1 / np.linalg.norm(v1, ord=2)
|
|
55
|
+
v2 = np.cross(a2, a3)
|
|
56
|
+
v2 = v2 / np.linalg.norm(v2, ord=2)
|
|
57
|
+
porm = np.sign((v1 * a3).sum(-1))
|
|
58
|
+
angle = np.arccos((v1*v2).sum(-1) / ((v1**2).sum(-1) * (v2**2).sum(-1))**0.5)
|
|
59
|
+
if not porm == 0:
|
|
60
|
+
angle = angle * porm
|
|
61
|
+
|
|
62
|
+
dihedral_angle_deg = np.degrees(angle)
|
|
63
|
+
|
|
64
|
+
return dihedral_angle_deg
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def read_xyz_file(self, file_name):
|
|
68
|
+
with open(file_name,"r") as f:
|
|
69
|
+
words = f.readlines()
|
|
70
|
+
mole_struct_list = []
|
|
71
|
+
|
|
72
|
+
for word in words[1:]:
|
|
73
|
+
mole_struct_list.append(word.split())
|
|
74
|
+
return mole_struct_list
|
|
75
|
+
|
|
76
|
+
def Data_extract(self, file, atom_numbers):
|
|
77
|
+
data_list = []
|
|
78
|
+
data_name_list = []
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
mole_struct_list = self.read_xyz_file(file)
|
|
83
|
+
DBD_list = []
|
|
84
|
+
DBD_name_list = []
|
|
85
|
+
#print(file, atom_numbers)
|
|
86
|
+
if len(atom_numbers) > 1:
|
|
87
|
+
for a1, a2 in list(itertools.combinations(atom_numbers,2)):
|
|
88
|
+
#print(a1, a2)
|
|
89
|
+
try:
|
|
90
|
+
distance = self.calculate_distance(mole_struct_list[int(a1) - 1][1:4], mole_struct_list[int(a2) - 1][1:4])
|
|
91
|
+
DBD_name_list.append("Distance ("+str(a1)+"-"+str(a2)+") [ang.]")
|
|
92
|
+
DBD_list.append(distance)
|
|
93
|
+
|
|
94
|
+
except Exception as e:
|
|
95
|
+
print(e)
|
|
96
|
+
DBD_name_list.append("Distance ("+str(a1)+"-"+str(a2)+") [ang.]")
|
|
97
|
+
DBD_list.append("nan")
|
|
98
|
+
|
|
99
|
+
if len(atom_numbers) > 2:
|
|
100
|
+
for a1, a2, a3 in list(itertools.permutations(atom_numbers,3)):
|
|
101
|
+
try:
|
|
102
|
+
bond_angle = self.calculate_bond_angle(mole_struct_list[int(a1)-1][1:4], mole_struct_list[int(a2)-1][1:4], mole_struct_list[int(a3)-1][1:4])
|
|
103
|
+
DBD_name_list.append("Bond_angle ("+str(a1)+"-"+str(a2)+"-"+str(a3)+") [deg.]")
|
|
104
|
+
DBD_list.append(bond_angle)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
print(e)
|
|
107
|
+
DBD_name_list.append("Bond_angle ("+str(a1)+"-"+str(a2)+"-"+str(a3)+") [deg.]")
|
|
108
|
+
DBD_list.append("nan")
|
|
109
|
+
|
|
110
|
+
if len(atom_numbers) > 3:
|
|
111
|
+
for a1, a2, a3, a4 in list(itertools.permutations(atom_numbers,4)):
|
|
112
|
+
try:
|
|
113
|
+
dihedral_angle = self.calculate_dihedral_angle(mole_struct_list[int(a1)-1][1:4], mole_struct_list[int(a2)-1][1:4],mole_struct_list[int(a3)-1][1:4], mole_struct_list[int(a4)-1][1:4])
|
|
114
|
+
DBD_name_list.append("Dihedral_angle ("+str(a1)+"-"+str(a2)+"-"+str(a3)+"-"+str(a4)+") [deg.]")
|
|
115
|
+
DBD_list.append(dihedral_angle)
|
|
116
|
+
except Exception as e:
|
|
117
|
+
print(e)
|
|
118
|
+
DBD_name_list.append("Dihedral_angle ("+str(a1)+"-"+str(a2)+"-"+str(a3)+"-"+str(a4)+") [deg.]")
|
|
119
|
+
DBD_list.append("nan")
|
|
120
|
+
|
|
121
|
+
data_list = DBD_list
|
|
122
|
+
|
|
123
|
+
data_name_list = DBD_name_list
|
|
124
|
+
return data_list, data_name_list
|
|
125
|
+
|
|
126
|
+
def hess2mwhess(hessian, element_list):
|
|
127
|
+
elem_mass = np.array([atomic_mass(elem) for elem in element_list], dtype="float64")
|
|
128
|
+
M = np.diag(np.repeat(elem_mass, 3))
|
|
129
|
+
M_minus_sqrt = np.diag(np.repeat(elem_mass, 3) ** (-0.5))
|
|
130
|
+
mw_hessian = np.dot(np.dot(M_minus_sqrt, hessian), M_minus_sqrt)#mw = mass weighted
|
|
131
|
+
return mw_hessian
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class Calculationtools:
|
|
135
|
+
def __init__(self):
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
def calc_center(self, geomerty, element_list=[]):#geomerty:Bohr
|
|
139
|
+
center = np.array([0.0, 0.0, 0.0], dtype="float64")
|
|
140
|
+
for i in range(len(geomerty)):
|
|
141
|
+
|
|
142
|
+
center += geomerty[i]
|
|
143
|
+
center /= len(geomerty)
|
|
144
|
+
|
|
145
|
+
return center
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def calc_center_of_mass(self, geomerty, element_list):#geomerty:Bohr
|
|
149
|
+
center_of_mass = np.array([0.0, 0.0, 0.0], dtype="float64")
|
|
150
|
+
elem_mass = np.array([atomic_mass(elem) for elem in element_list], dtype="float64")
|
|
151
|
+
|
|
152
|
+
for i in range(len(elem_mass)):
|
|
153
|
+
|
|
154
|
+
center_of_mass += geomerty[i] * elem_mass[i]
|
|
155
|
+
|
|
156
|
+
center_of_mass /= np.sum(elem_mass)
|
|
157
|
+
|
|
158
|
+
return center_of_mass
|
|
159
|
+
|
|
160
|
+
def coord2massweightedcoord(self, geomerty, element_list):
|
|
161
|
+
#output: Mass-weighted coordinates adjusted to the origin of the mass-weighted point
|
|
162
|
+
center_of_mass = self.calc_center_of_mass(geomerty, element_list)
|
|
163
|
+
geomerty -= center_of_mass
|
|
164
|
+
elem_mass = np.array([atomic_mass(elem) for elem in element_list], dtype="float64")
|
|
165
|
+
mass_weighted_coord = geomerty * 0.0
|
|
166
|
+
for i in range(len(geomerty)):
|
|
167
|
+
mass_weighted_coord[i] = copy.copy(geomerty[i] * np.sqrt(elem_mass[i]))
|
|
168
|
+
return mass_weighted_coord
|
|
169
|
+
|
|
170
|
+
def project_out_hess_tr_and_rot(self, hessian, element_list, geometry, display_eigval=True):#covert coordination to mass-weighted coordination
|
|
171
|
+
natoms = len(element_list)
|
|
172
|
+
|
|
173
|
+
# Move to center of mass
|
|
174
|
+
geometry = geometry - self.calc_center_of_mass(geometry, element_list)
|
|
175
|
+
|
|
176
|
+
# Calculate atomic masses
|
|
177
|
+
elem_mass = np.array([atomic_mass(elem) for elem in element_list], dtype="float64")
|
|
178
|
+
|
|
179
|
+
# Mass-weighting matrices
|
|
180
|
+
m_sqrt = np.repeat(elem_mass, 3) ** 0.5
|
|
181
|
+
M_minus_sqrt = np.diag(np.repeat(elem_mass, 3) ** (-0.5))
|
|
182
|
+
|
|
183
|
+
# Convert to mass-weighted Hessian
|
|
184
|
+
mw_hessian = np.dot(np.dot(M_minus_sqrt, hessian), M_minus_sqrt)
|
|
185
|
+
|
|
186
|
+
# Initialize arrays for translation and rotation vectors
|
|
187
|
+
tr_vectors = np.zeros((3, 3 * natoms))
|
|
188
|
+
rot_vectors = np.zeros((3, 3 * natoms))
|
|
189
|
+
|
|
190
|
+
# Create mass-weighted translation vectors
|
|
191
|
+
for i in range(3):
|
|
192
|
+
tr_vectors[i, i::3] = m_sqrt[i::3]
|
|
193
|
+
|
|
194
|
+
# Create mass-weighted rotation vectors
|
|
195
|
+
for atom in range(natoms):
|
|
196
|
+
x, y, z = geometry[atom]
|
|
197
|
+
mass_sqrt = m_sqrt[3*atom]
|
|
198
|
+
|
|
199
|
+
# Rotation around x-axis: (0, -z, y)
|
|
200
|
+
rot_vectors[0, 3*atom:3*atom+3] = np.array([0.0, -z, y]) * mass_sqrt
|
|
201
|
+
|
|
202
|
+
# Rotation around y-axis: (z, 0, -x)
|
|
203
|
+
rot_vectors[1, 3*atom:3*atom+3] = np.array([z, 0.0, -x]) * mass_sqrt
|
|
204
|
+
|
|
205
|
+
# Rotation around z-axis: (-y, x, 0)
|
|
206
|
+
rot_vectors[2, 3*atom:3*atom+3] = np.array([-y, x, 0.0]) * mass_sqrt
|
|
207
|
+
|
|
208
|
+
# Combine translation and rotation vectors
|
|
209
|
+
TR_vectors = np.vstack([tr_vectors, rot_vectors])
|
|
210
|
+
|
|
211
|
+
# Gram-Schmidt orthonormalization with improved numerical stability
|
|
212
|
+
def gram_schmidt(vectors):
|
|
213
|
+
basis = []
|
|
214
|
+
for v in vectors:
|
|
215
|
+
w = v.copy()
|
|
216
|
+
for b in basis:
|
|
217
|
+
w -= np.dot(v, b) * b
|
|
218
|
+
norm = np.linalg.norm(w)
|
|
219
|
+
if norm > 1e-10: # Threshold for linear independence
|
|
220
|
+
basis.append(w / norm)
|
|
221
|
+
return np.array(basis)
|
|
222
|
+
|
|
223
|
+
# Orthonormalize the translation and rotation vectors
|
|
224
|
+
TR_vectors = gram_schmidt(TR_vectors)
|
|
225
|
+
|
|
226
|
+
# Calculate projection matrix
|
|
227
|
+
P = np.eye(3 * natoms)
|
|
228
|
+
for vector in TR_vectors:
|
|
229
|
+
P -= np.outer(vector, vector)
|
|
230
|
+
|
|
231
|
+
# Project the mass-weighted Hessian
|
|
232
|
+
mw_hess_proj = np.dot(np.dot(P.T, mw_hessian), P)
|
|
233
|
+
|
|
234
|
+
# Ensure symmetry (numerical stability)
|
|
235
|
+
mw_hess_proj = (mw_hess_proj + mw_hess_proj.T) / 2
|
|
236
|
+
|
|
237
|
+
if display_eigval:
|
|
238
|
+
eigenvalues, _ = np.linalg.eigh(mw_hess_proj)
|
|
239
|
+
eigenvalues = np.sort(eigenvalues)
|
|
240
|
+
# Stricter threshold for eigenvalue filtering
|
|
241
|
+
idx_eigenvalues = np.where(np.abs(eigenvalues) > 1e-7)[0]
|
|
242
|
+
print(f"EIGENVALUES (MASS-WEIGHTED COORDINATE, NUMBER OF VALUES: {len(idx_eigenvalues)}):")
|
|
243
|
+
for i in range(0, len(idx_eigenvalues), 6):
|
|
244
|
+
tmp_arr = eigenvalues[idx_eigenvalues[i:i+6]]
|
|
245
|
+
print(" ".join(f"{val:12.8f}" for val in tmp_arr))
|
|
246
|
+
|
|
247
|
+
return mw_hess_proj
|
|
248
|
+
|
|
249
|
+
def project_out_hess_tr_and_rot_for_coord(self, hessian, element_list, geometry, display_eigval=True):#do not consider atomic mass
|
|
250
|
+
def gram_schmidt(vectors):
|
|
251
|
+
basis = []
|
|
252
|
+
for v in vectors:
|
|
253
|
+
w = v.copy()
|
|
254
|
+
for b in basis:
|
|
255
|
+
w -= np.dot(v, b) * b
|
|
256
|
+
norm = np.linalg.norm(w)
|
|
257
|
+
if norm > 1e-10:
|
|
258
|
+
basis.append(w / norm)
|
|
259
|
+
return np.array(basis)
|
|
260
|
+
|
|
261
|
+
natoms = len(element_list)
|
|
262
|
+
# Center the geometry
|
|
263
|
+
geometry = geometry - self.calc_center(geometry, element_list)
|
|
264
|
+
|
|
265
|
+
# Initialize arrays for translation and rotation vectors
|
|
266
|
+
tr_vectors = np.zeros((3, 3 * natoms))
|
|
267
|
+
rot_vectors = np.zeros((3, 3 * natoms))
|
|
268
|
+
|
|
269
|
+
# Create translation vectors (mass-weighted normalization is not used as specified)
|
|
270
|
+
for i in range(3):
|
|
271
|
+
tr_vectors[i, i::3] = 1.0
|
|
272
|
+
|
|
273
|
+
# Create rotation vectors
|
|
274
|
+
for atom in range(natoms):
|
|
275
|
+
# Get atom coordinates
|
|
276
|
+
x, y, z = geometry[atom]
|
|
277
|
+
|
|
278
|
+
# Rotation around x-axis: (0, -z, y)
|
|
279
|
+
rot_vectors[0, 3*atom:3*atom+3] = np.array([0.0, -z, y])
|
|
280
|
+
|
|
281
|
+
# Rotation around y-axis: (z, 0, -x)
|
|
282
|
+
rot_vectors[1, 3*atom:3*atom+3] = np.array([z, 0.0, -x])
|
|
283
|
+
|
|
284
|
+
# Rotation around z-axis: (-y, x, 0)
|
|
285
|
+
rot_vectors[2, 3*atom:3*atom+3] = np.array([-y, x, 0.0])
|
|
286
|
+
|
|
287
|
+
# Combine translation and rotation vectors
|
|
288
|
+
TR_vectors = np.vstack([tr_vectors, rot_vectors])
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# Orthonormalize the translation and rotation vectors
|
|
293
|
+
TR_vectors = gram_schmidt(TR_vectors)
|
|
294
|
+
|
|
295
|
+
# Calculate projection matrix
|
|
296
|
+
P = np.eye(3 * natoms)
|
|
297
|
+
for vector in TR_vectors:
|
|
298
|
+
P -= np.outer(vector, vector)
|
|
299
|
+
|
|
300
|
+
# Project the Hessian
|
|
301
|
+
hess_proj = np.dot(np.dot(P.T, hessian), P)
|
|
302
|
+
|
|
303
|
+
# Make the projected Hessian symmetric (numerical stability)
|
|
304
|
+
hess_proj = (hess_proj + hess_proj.T) / 2
|
|
305
|
+
|
|
306
|
+
if display_eigval:
|
|
307
|
+
eigenvalues, _ = np.linalg.eigh(hess_proj)
|
|
308
|
+
eigenvalues = np.sort(eigenvalues)
|
|
309
|
+
# Filter out near-zero eigenvalues
|
|
310
|
+
idx_eigenvalues = np.where(np.abs(eigenvalues) > 1e-10)[0]
|
|
311
|
+
print(f"EIGENVALUES (NORMAL COORDINATE, NUMBER OF VALUES: {len(idx_eigenvalues)}):")
|
|
312
|
+
for i in range(0, len(idx_eigenvalues), 6):
|
|
313
|
+
tmp_arr = eigenvalues[idx_eigenvalues[i:i+6]]
|
|
314
|
+
print(" ".join(f"{val:12.8f}" for val in tmp_arr))
|
|
315
|
+
|
|
316
|
+
return hess_proj
|
|
317
|
+
|
|
318
|
+
def check_atom_connectivity(self, mol_list, element_list, atom_num, covalent_radii_threshold_scale=1.2):#mol_list:ang.
|
|
319
|
+
# Convert molecular coordinates to numpy array
|
|
320
|
+
coords = np.array(mol_list, dtype=np.float64)
|
|
321
|
+
|
|
322
|
+
# Build KD-Tree for efficient nearest neighbor searches
|
|
323
|
+
kdtree = cKDTree(coords)
|
|
324
|
+
|
|
325
|
+
# Initialize arrays for tracking
|
|
326
|
+
n_atoms = len(mol_list)
|
|
327
|
+
connected_atoms = [atom_num]
|
|
328
|
+
searched_atoms = []
|
|
329
|
+
|
|
330
|
+
# Pre-calculate covalent radii for each element to avoid repeated lookups
|
|
331
|
+
cov_radii = [covalent_radii_lib(element) * UnitValueLib().bohr2angstroms for element in element_list]
|
|
332
|
+
|
|
333
|
+
while True:
|
|
334
|
+
search_progress = False
|
|
335
|
+
for i in connected_atoms:
|
|
336
|
+
if i in searched_atoms:
|
|
337
|
+
continue
|
|
338
|
+
|
|
339
|
+
# Calculate max possible bond distance for this atom
|
|
340
|
+
# This is a conservative estimate to limit initial search radius
|
|
341
|
+
max_cov_radius = max([cov_radii[i] + cov_radii[j] for j in range(n_atoms)]) * covalent_radii_threshold_scale
|
|
342
|
+
|
|
343
|
+
# Query the KD-Tree for potential neighbors within the max bond distance
|
|
344
|
+
potential_neighbors = kdtree.query_ball_point(coords[i], max_cov_radius)
|
|
345
|
+
|
|
346
|
+
# Check each potential neighbor more precisely
|
|
347
|
+
for j in potential_neighbors:
|
|
348
|
+
if j == i or j in connected_atoms:
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
# Calculate exact threshold for this specific pair
|
|
352
|
+
covalent_dist_threshold = covalent_radii_threshold_scale * (cov_radii[i] + cov_radii[j])
|
|
353
|
+
|
|
354
|
+
# Calculate distance
|
|
355
|
+
dist = np.linalg.norm(coords[i] - coords[j])
|
|
356
|
+
|
|
357
|
+
if dist < covalent_dist_threshold:
|
|
358
|
+
connected_atoms.append(j)
|
|
359
|
+
search_progress = True
|
|
360
|
+
|
|
361
|
+
searched_atoms.append(i)
|
|
362
|
+
search_progress = True
|
|
363
|
+
|
|
364
|
+
if not search_progress or len(connected_atoms) == len(searched_atoms):
|
|
365
|
+
break
|
|
366
|
+
|
|
367
|
+
return sorted(connected_atoms)
|
|
368
|
+
|
|
369
|
+
def calc_fragm_distance_matrix(self, fragm_coord_list):
|
|
370
|
+
distance_matrix = np.zeros((len(fragm_coord_list), len(fragm_coord_list)))
|
|
371
|
+
for i in range(len(fragm_coord_list)):
|
|
372
|
+
for j in range(len(fragm_coord_list)):
|
|
373
|
+
if i < j:
|
|
374
|
+
continue
|
|
375
|
+
dist = np.linalg.norm(self.calc_center(fragm_coord_list[i], []) - self.calc_center(fragm_coord_list[j], []))
|
|
376
|
+
distance_matrix[i][j] = dist
|
|
377
|
+
distance_matrix[j][i] = dist
|
|
378
|
+
|
|
379
|
+
return distance_matrix
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def calc_fragm_distance(self, geom_num_list, fragm_1_num, fragm_2_num):
|
|
383
|
+
fragm_1_coord = np.array([0.0, 0.0, 0.0], dtype="float64")
|
|
384
|
+
fragm_2_coord = np.array([0.0, 0.0, 0.0], dtype="float64")
|
|
385
|
+
|
|
386
|
+
for num in fragm_1_num:
|
|
387
|
+
fragm_1_coord += geom_num_list[num]
|
|
388
|
+
|
|
389
|
+
fragm_1_coord /= len(fragm_1_num)
|
|
390
|
+
|
|
391
|
+
for num in fragm_2_num:
|
|
392
|
+
fragm_2_coord += geom_num_list[num]
|
|
393
|
+
|
|
394
|
+
fragm_2_coord /= len(fragm_2_num)
|
|
395
|
+
|
|
396
|
+
dist = np.linalg.norm(fragm_1_coord - fragm_2_coord)
|
|
397
|
+
|
|
398
|
+
return dist
|
|
399
|
+
|
|
400
|
+
def calc_geodesic_distance(self, geom_num_list_1, geom_num_list_2):
|
|
401
|
+
#doi:10.1002/jcc.27030
|
|
402
|
+
geodesic_dist_mat = np.ones((len(geom_num_list_1), 3))
|
|
403
|
+
dist = np.linalg.norm(geom_num_list_2 - geom_num_list_1)
|
|
404
|
+
geodesic_dist_mat *= dist / np.sqrt(3 * len(geom_num_list_1))
|
|
405
|
+
return geodesic_dist_mat
|
|
406
|
+
|
|
407
|
+
def calc_euclidean_distance(self, geom_num_list_1, geom_num_list_2):
|
|
408
|
+
#doi:10.1002/jcc.27030
|
|
409
|
+
euclidean_dist_mat = geom_num_list_2 - geom_num_list_1
|
|
410
|
+
return euclidean_dist_mat
|
|
411
|
+
|
|
412
|
+
def kabsch_algorithm(self, P, Q):
|
|
413
|
+
#scipy.spatial.transform.Rotation.align_vectors
|
|
414
|
+
centroid_P = np.array([np.mean(P.T[0]), np.mean(P.T[1]), np.mean(P.T[2])], dtype="float64")
|
|
415
|
+
centroid_Q = np.array([np.mean(Q.T[0]), np.mean(Q.T[1]), np.mean(Q.T[2])], dtype="float64")
|
|
416
|
+
P -= centroid_P
|
|
417
|
+
Q -= centroid_Q
|
|
418
|
+
H = np.dot(P.T, Q)
|
|
419
|
+
U, S, Vt = np.linalg.svd(H)
|
|
420
|
+
R = np.dot(Vt.T, U.T)
|
|
421
|
+
if np.linalg.det(R) < 0:
|
|
422
|
+
Vt[-1,:] *= -1
|
|
423
|
+
R = np.dot(Vt.T, U.T)
|
|
424
|
+
P = np.dot(R, P.T).T
|
|
425
|
+
return P, Q
|
|
426
|
+
|
|
427
|
+
def gen_n_dinensional_rot_matrix(self, vector_1, vector_2):
|
|
428
|
+
#Zhelezov NRMG algorithm (doi:10.5923/j.ajcam.20170702.04) This implementation may be not correct.
|
|
429
|
+
dimension_1 = len(vector_1)
|
|
430
|
+
dimension_2 = len(vector_2)
|
|
431
|
+
assert dimension_1 == dimension_2
|
|
432
|
+
R_1 = np.eye((dimension_1))
|
|
433
|
+
R_2 = np.eye((dimension_2))
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
step = 1
|
|
438
|
+
|
|
439
|
+
while step < dimension_1:
|
|
440
|
+
A_1 = np.eye((dimension_1))
|
|
441
|
+
n = 1
|
|
442
|
+
#print(step)
|
|
443
|
+
while n <= dimension_1 - step:
|
|
444
|
+
#print(n)
|
|
445
|
+
#print(vector_1[n + step - 1])
|
|
446
|
+
r2 = vector_1[n - 1] ** 2 + vector_1[n + step - 1] ** 2
|
|
447
|
+
if r2 > 0:
|
|
448
|
+
r = r2 ** 0.5
|
|
449
|
+
p_cos = vector_1[n - 1] / r
|
|
450
|
+
|
|
451
|
+
p_sin = -1 * vector_1[n + step - 1] / r
|
|
452
|
+
A_1[n - 1][n - 1] = p_cos.item()
|
|
453
|
+
A_1[n - 1][n + step - 1] = -1 * p_sin.item()
|
|
454
|
+
A_1[n + step - 1][n - 1] = p_sin.item()
|
|
455
|
+
A_1[n + step - 1][n + step - 1] = p_cos.item()
|
|
456
|
+
n += 2 * step
|
|
457
|
+
step *= 2
|
|
458
|
+
vector_1 = np.dot(A_1, vector_1)
|
|
459
|
+
R_1 = np.dot(A_1, R_1)
|
|
460
|
+
|
|
461
|
+
step = 1
|
|
462
|
+
|
|
463
|
+
while step < dimension_2:
|
|
464
|
+
A_2 = np.eye((dimension_2))
|
|
465
|
+
n = 1
|
|
466
|
+
while n <= dimension_2 - step:
|
|
467
|
+
r2 = vector_2[n - 1] ** 2 + vector_2[n + step - 1] ** 2
|
|
468
|
+
if r2 > 0:
|
|
469
|
+
r = r2 ** 0.5
|
|
470
|
+
p_cos = vector_2[n - 1] / r
|
|
471
|
+
p_sin = -1 * vector_2[n + step - 1] / r
|
|
472
|
+
A_2[n - 1][n - 1] = p_cos.item()
|
|
473
|
+
A_2[n - 1][n + step - 1] = -1 * p_sin.item()
|
|
474
|
+
A_2[n + step - 1][n - 1] = p_sin.item()
|
|
475
|
+
A_2[n + step - 1][n + step - 1] = p_cos.item()
|
|
476
|
+
n += 2 * step
|
|
477
|
+
step *= 2
|
|
478
|
+
vector_2 = np.dot(A_2, vector_2)
|
|
479
|
+
R_2 = np.dot(A_2, R_2)
|
|
480
|
+
#print(R_1, R_2)
|
|
481
|
+
R_12 = np.dot(R_2.T, R_1)
|
|
482
|
+
#vector_1 -> vector_2's direction
|
|
483
|
+
return R_12
|
|
484
|
+
|
|
485
|
+
def calc_multi_dim_vec_angle(self, vec_1, vec_2):
|
|
486
|
+
|
|
487
|
+
angle = np.arccos(np.sum(vec_1 * vec_2) / (np.linalg.norm(vec_1) * np.linalg.norm(vec_2)) + 1e-8)
|
|
488
|
+
|
|
489
|
+
return angle
|
|
490
|
+
|
|
491
|
+
def calc_normalized_distance_list(geom_num_list, element_list, tgt_atoms=None):
|
|
492
|
+
if tgt_atoms is not None:
|
|
493
|
+
atom_list = [i for i in tgt_atoms]
|
|
494
|
+
else:
|
|
495
|
+
atom_list = [i for i in range(len(geom_num_list))]
|
|
496
|
+
|
|
497
|
+
norm_distance_list = []
|
|
498
|
+
|
|
499
|
+
for i, j in itertools.combinations(atom_list, 2):#(0, 1) (0, 2) ... (natoms-2, natoms-1)
|
|
500
|
+
elem_i = element_list[i]
|
|
501
|
+
elem_j = element_list[j]
|
|
502
|
+
covalent_length = covalent_radii_lib(elem_i) + covalent_radii_lib(elem_j)
|
|
503
|
+
norm_distance = np.linalg.norm(geom_num_list[i] - geom_num_list[j]) / covalent_length
|
|
504
|
+
norm_distance_list.append(norm_distance)
|
|
505
|
+
norm_distance_list = np.array(norm_distance_list)
|
|
506
|
+
return norm_distance_list
|
|
507
|
+
|
|
508
|
+
def return_pair_idx(i, j):
|
|
509
|
+
ii = max(i, j) + 1
|
|
510
|
+
jj = min(i, j) + 1
|
|
511
|
+
pair_idx = int(ii * (ii - 1) / 2 - (ii - jj)) -1
|
|
512
|
+
return pair_idx
|
|
513
|
+
|
|
514
|
+
def calc_bond_length_from_vec(vector1, vector2):
|
|
515
|
+
distance = np.linalg.norm(vector1 - vector2)
|
|
516
|
+
return distance
|
|
517
|
+
|
|
518
|
+
def torch_calc_angle_from_vec(vector1, vector2):
|
|
519
|
+
magnitude1 = torch.linalg.norm(vector1)
|
|
520
|
+
if torch.abs(magnitude1) < 1e-15:
|
|
521
|
+
magnitude1 = magnitude1 + 1e-15
|
|
522
|
+
magnitude2 = torch.linalg.norm(vector2)
|
|
523
|
+
if torch.abs(magnitude2) < 1e-15:
|
|
524
|
+
magnitude2 = magnitude2 + 1e-15
|
|
525
|
+
|
|
526
|
+
dot_product = torch.matmul(vector1, vector2)
|
|
527
|
+
cos_theta = dot_product / (magnitude1 * magnitude2)
|
|
528
|
+
theta = torch.arccos(cos_theta)
|
|
529
|
+
return theta
|
|
530
|
+
|
|
531
|
+
def calc_angle_from_vec(vector1, vector2):
|
|
532
|
+
magnitude1 = np.linalg.norm(vector1)
|
|
533
|
+
if np.abs(magnitude1) < 1e-15:
|
|
534
|
+
magnitude1 += 1e-15
|
|
535
|
+
magnitude2 = np.linalg.norm(vector2)
|
|
536
|
+
if np.abs(magnitude2) < 1e-15:
|
|
537
|
+
magnitude2 += 1e-15
|
|
538
|
+
dot_product = np.matmul(vector1, vector2)
|
|
539
|
+
cos_theta = dot_product / (magnitude1 * magnitude2)
|
|
540
|
+
theta = np.arccos(cos_theta)
|
|
541
|
+
return theta
|
|
542
|
+
|
|
543
|
+
def torch_calc_dihedral_angle_from_vec(vector1, vector2, vector3):
|
|
544
|
+
v1 = torch.linalg.cross(vector1, vector2)
|
|
545
|
+
v2 = torch.linalg.cross(vector2, vector3)
|
|
546
|
+
norm_v1 = torch.linalg.norm(v1)
|
|
547
|
+
if torch.abs(norm_v1) < 1e-15:
|
|
548
|
+
norm_v1 = norm_v1 + 1e-15
|
|
549
|
+
norm_v2 = torch.linalg.norm(v2)
|
|
550
|
+
if torch.abs(norm_v2) < 1e-15:
|
|
551
|
+
norm_v2 = norm_v2 + 1e-15
|
|
552
|
+
|
|
553
|
+
cos_theta = torch.sum(v1*v2) / (norm_v1 * norm_v2)
|
|
554
|
+
angle = torch.arccos(cos_theta)
|
|
555
|
+
sign = torch.sign(torch.sum(torch.linalg.cross(v1 / norm_v1, v2 / norm_v2) * vector2))
|
|
556
|
+
if sign != 0:
|
|
557
|
+
angle = -1 * angle * sign
|
|
558
|
+
return angle
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def change_torsion_angle_both_side(coordinates, atom_idx1, atom_idx2, atom_idx3, atom_idx4, target_torsion):#rad:target_torsion
|
|
562
|
+
A = coordinates[atom_idx1]
|
|
563
|
+
B = coordinates[atom_idx2]
|
|
564
|
+
C = coordinates[atom_idx3]
|
|
565
|
+
D = coordinates[atom_idx4]
|
|
566
|
+
current_torsion = calc_dihedral_angle_from_vec(A - B, B - C, C - D)
|
|
567
|
+
|
|
568
|
+
torsion_diff = target_torsion - current_torsion
|
|
569
|
+
|
|
570
|
+
BC = C - B
|
|
571
|
+
new_D = rotate_atom(D, C, BC, torsion_diff * 0.5)
|
|
572
|
+
new_A = rotate_atom(A, B, BC, -1*torsion_diff * 0.5)
|
|
573
|
+
coordinates[atom_idx4] = new_D
|
|
574
|
+
coordinates[atom_idx1] = new_A
|
|
575
|
+
|
|
576
|
+
return coordinates
|
|
577
|
+
|
|
578
|
+
def change_bond_angle_both_side(coordinates, atom_idx1, atom_idx2, atom_idx3, target_angle):#rad:target_angle
|
|
579
|
+
A = coordinates[atom_idx1]
|
|
580
|
+
B = coordinates[atom_idx2]
|
|
581
|
+
C = coordinates[atom_idx3]
|
|
582
|
+
BA = A - B
|
|
583
|
+
BC = C - B
|
|
584
|
+
current_angle_rad = calc_angle_from_vec(BA, BC)
|
|
585
|
+
rotation_axis = np.cross(BA, BC)
|
|
586
|
+
rotation_axis = rotation_axis / np.linalg.norm(rotation_axis)
|
|
587
|
+
|
|
588
|
+
angle_diff = target_angle - current_angle_rad
|
|
589
|
+
|
|
590
|
+
new_A = rotate_atom(A, B, rotation_axis, -angle_diff / 2.0)
|
|
591
|
+
new_C = rotate_atom(C, B, rotation_axis, angle_diff / 2.0)
|
|
592
|
+
coordinates[atom_idx1] = new_A
|
|
593
|
+
coordinates[atom_idx3] = new_C
|
|
594
|
+
return coordinates
|
|
595
|
+
|
|
596
|
+
def calc_dihedral_angle_from_vec(vector1, vector2, vector3):
|
|
597
|
+
v1 = np.cross(vector1, vector2)
|
|
598
|
+
v2 = np.cross(vector2, vector3)
|
|
599
|
+
norm_v1 = np.linalg.norm(v1)
|
|
600
|
+
if np.abs(norm_v1) < 1e-15:
|
|
601
|
+
norm_v1 += 1e-15
|
|
602
|
+
if np.abs(norm_v2) < 1e-15:
|
|
603
|
+
norm_v2 += 1e-15
|
|
604
|
+
|
|
605
|
+
cos_theta = np.sum(v1*v2) / (norm_v1 * norm_v2)
|
|
606
|
+
angle = np.abs(np.arccos(cos_theta))
|
|
607
|
+
sign = np.sign(np.dot(np.cross(v1 / norm_v1, v2 / norm_v2), vector2))
|
|
608
|
+
if sign != 0:
|
|
609
|
+
angle = -1 * angle * sign
|
|
610
|
+
return angle
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def rotate_atom(coord, axis_point, axis_direction, angle):
|
|
615
|
+
axis_unit = axis_direction / np.linalg.norm(axis_direction)
|
|
616
|
+
translated_coord = coord - axis_point
|
|
617
|
+
cos_angle = np.cos(angle)
|
|
618
|
+
sin_angle = np.sin(angle)
|
|
619
|
+
rotation_matrix = np.array([
|
|
620
|
+
[cos_angle + axis_unit[0]**2 * (1 - cos_angle), axis_unit[0]*axis_unit[1]*(1 - cos_angle) - axis_unit[2]*sin_angle, axis_unit[0]*axis_unit[2]*(1 - cos_angle) + axis_unit[1]*sin_angle],
|
|
621
|
+
[axis_unit[1]*axis_unit[0]*(1 - cos_angle) + axis_unit[2]*sin_angle, cos_angle + axis_unit[1]**2 * (1 - cos_angle), axis_unit[1]*axis_unit[2]*(1 - cos_angle) - axis_unit[0]*sin_angle],
|
|
622
|
+
[axis_unit[2]*axis_unit[0]*(1 - cos_angle) - axis_unit[1]*sin_angle, axis_unit[2]*axis_unit[1]*(1 - cos_angle) + axis_unit[0]*sin_angle, cos_angle + axis_unit[2]**2 * (1 - cos_angle)]
|
|
623
|
+
])
|
|
624
|
+
rotated_coord = np.dot(rotation_matrix, translated_coord)
|
|
625
|
+
return rotated_coord + axis_point
|
|
626
|
+
|
|
627
|
+
def torch_calc_outofplain_angle_from_vec(vector1, vector2, vector3):
|
|
628
|
+
v1 = torch.linalg.cross(vector1, vector2)
|
|
629
|
+
magnitude1 = torch.linalg.norm(v1)
|
|
630
|
+
if torch.abs(magnitude1) < 1e-15:
|
|
631
|
+
magnitude1 = magnitude1 + 1e-15
|
|
632
|
+
magnitude2 = torch.linalg.norm(vector3)
|
|
633
|
+
if torch.abs(magnitude2) < 1e-15:
|
|
634
|
+
magnitude2 = magnitude2 + 1e-15
|
|
635
|
+
|
|
636
|
+
dot_product = torch.matmul(v1, vector3)
|
|
637
|
+
cos_theta = dot_product / (magnitude1 * magnitude2)
|
|
638
|
+
angle = torch.arccos(cos_theta)
|
|
639
|
+
return angle
|
|
640
|
+
|
|
641
|
+
def calc_outofplain_angle_from_vec(vector1, vector2, vector3):
|
|
642
|
+
v1 = np.cross(vector1, vector2)
|
|
643
|
+
|
|
644
|
+
magnitude1 = np.linalg.norm(v1)
|
|
645
|
+
if np.abs(magnitude1) < 1e-15:
|
|
646
|
+
magnitude1 += 1e-15
|
|
647
|
+
magnitude2 = np.linalg.norm(vector3)
|
|
648
|
+
if np.abs(magnitude2) < 1e-15:
|
|
649
|
+
magnitude2 += 1e-15
|
|
650
|
+
|
|
651
|
+
dot_product = np.matmul(v1, vector3)
|
|
652
|
+
cos_theta = dot_product / (magnitude1 * magnitude2)
|
|
653
|
+
angle = np.arccos(cos_theta)
|
|
654
|
+
return angle
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def output_partial_hess(hessian, atom_num_list, element_list, geometry):#hessian: ndarray 3N*3N, atom_num_list: list
|
|
658
|
+
partial_hess = np.zeros((3*len(atom_num_list), 3*len(atom_num_list)))
|
|
659
|
+
partial_geom = np.zeros((len(atom_num_list), 3))
|
|
660
|
+
partial_element_list = []
|
|
661
|
+
|
|
662
|
+
# Copy the relevant parts of the geometry and element list
|
|
663
|
+
for i in range(len(atom_num_list)):
|
|
664
|
+
partial_geom[i] = copy.copy(geometry[atom_num_list[i]-1])
|
|
665
|
+
partial_element_list.append(element_list[atom_num_list[i]-1])
|
|
666
|
+
|
|
667
|
+
# Copy the relevant parts of the Hessian matrix
|
|
668
|
+
for i, j in itertools.product(range(len(atom_num_list)), repeat=2):
|
|
669
|
+
for k in range(3):
|
|
670
|
+
for l in range(3):
|
|
671
|
+
partial_hess[3*i+k][3*j+l] = copy.copy(hessian[3*(atom_num_list[i]-1)+k][3*(atom_num_list[j]-1)+l])
|
|
672
|
+
|
|
673
|
+
return partial_hess, partial_geom, partial_element_list
|
|
674
|
+
|
|
675
|
+
def fragment_check(new_geometry, element_list):
|
|
676
|
+
atom_label_list = [i for i in range(len(new_geometry))]
|
|
677
|
+
fragm_atom_num_list = []
|
|
678
|
+
while len(atom_label_list) > 0:
|
|
679
|
+
tmp_fragm_list = Calculationtools().check_atom_connectivity(new_geometry, element_list, atom_label_list[0], covalent_radii_threshold_scale=1.2)
|
|
680
|
+
|
|
681
|
+
for j in tmp_fragm_list:
|
|
682
|
+
atom_label_list.remove(j)
|
|
683
|
+
fragm_atom_num_list.append(tmp_fragm_list)
|
|
684
|
+
|
|
685
|
+
print("\nfragment_list:", fragm_atom_num_list)
|
|
686
|
+
|
|
687
|
+
return fragm_atom_num_list
|
|
688
|
+
|
|
689
|
+
def rotate_molecule(geom, axis, angle):
|
|
690
|
+
#geom: ndarray, axis: str, angle: float (radian)
|
|
691
|
+
#axis: "x", "y", "z"
|
|
692
|
+
if axis == "x":
|
|
693
|
+
rot_matrix = np.array([[1.0, 0.0, 0.0],
|
|
694
|
+
[0.0, np.cos(angle), -np.sin(angle)],
|
|
695
|
+
[0.0, np.sin(angle), np.cos(angle)]], dtype="float64")
|
|
696
|
+
elif axis == "y":
|
|
697
|
+
rot_matrix = np.array([[np.cos(angle), 0.0, np.sin(angle)],
|
|
698
|
+
[0.0, 1.0, 0.0],
|
|
699
|
+
[-np.sin(angle), 0.0, np.cos(angle)]], dtype="float64")
|
|
700
|
+
elif axis == "z":
|
|
701
|
+
rot_matrix = np.array([[np.cos(angle), -np.sin(angle), 0.0],
|
|
702
|
+
[np.sin(angle), np.cos(angle), 0.0],
|
|
703
|
+
[0.0, 0.0, 1.0]], dtype="float64")
|
|
704
|
+
else:
|
|
705
|
+
print("Invalid axis.")
|
|
706
|
+
return
|
|
707
|
+
|
|
708
|
+
new_geom = np.dot(geom, rot_matrix)
|
|
709
|
+
return new_geom
|
|
710
|
+
|
|
711
|
+
def calc_partial_center(geometry, atom_num_list):
|
|
712
|
+
partial_center = np.array([0.0, 0.0, 0.0], dtype="float64")
|
|
713
|
+
|
|
714
|
+
for i in atom_num_list:
|
|
715
|
+
partial_center += geometry[i-1]
|
|
716
|
+
partial_center /= len(atom_num_list)
|
|
717
|
+
|
|
718
|
+
return partial_center
|
|
719
|
+
|
|
720
|
+
def torch_calc_partial_center(geometry, atom_num_list):
|
|
721
|
+
partial_center = torch.tensor([0.0, 0.0, 0.0], dtype=torch.float64, requires_grad=True)
|
|
722
|
+
for i in atom_num_list:
|
|
723
|
+
partial_center = partial_center + geometry[i-1]
|
|
724
|
+
partial_center /= len(atom_num_list)
|
|
725
|
+
return partial_center
|
|
726
|
+
|
|
727
|
+
def project_optional_vector_for_grad(gradient, vector):#gradient:ndarray (3*natoms, 1), vector:ndarray (3*natoms, 1)
|
|
728
|
+
unit_vec = vector / np.linalg.norm(vector)
|
|
729
|
+
P_matrix = np.eye((len(gradient))) -1 * np.dot(unit_vec, unit_vec.T)
|
|
730
|
+
gradient_proj = np.dot(P_matrix, gradient).reshape(len(vector), 1)
|
|
731
|
+
return gradient_proj #gradient_proj:ndarray (3*natoms, 1)
|
|
732
|
+
|
|
733
|
+
def project_optional_vector_for_hess(hessian, vector):#hessian:ndarray (3*natoms, 3*natoms), vector:ndarray (3*natoms, 1)
|
|
734
|
+
hess_length = len(vector)
|
|
735
|
+
identity_matrix = np.eye(hess_length)
|
|
736
|
+
LL = np.dot(vector, vector.T)
|
|
737
|
+
E_LL = identity_matrix - LL
|
|
738
|
+
hessian_proj = np.dot(np.dot(E_LL, hessian), E_LL)
|
|
739
|
+
return hessian_proj #hessian:ndarray (3*natoms, 3*natoms)
|
|
740
|
+
|
|
741
|
+
|
|
742
|
+
def move_atom_distance_one_side(geom_num_list, atom1, atom2, distance):
|
|
743
|
+
vec = geom_num_list[atom2] - geom_num_list[atom1]
|
|
744
|
+
norm_vec = np.linalg.norm(vec)
|
|
745
|
+
unit_vec = vec / norm_vec
|
|
746
|
+
geom_num_list[atom2] = geom_num_list[atom2] + distance * unit_vec
|
|
747
|
+
return geom_num_list
|
|
748
|
+
|
|
749
|
+
def change_atom_distance_both_side(geom_num_list, atom1, atom2, distance):
|
|
750
|
+
vec = geom_num_list[atom2] - geom_num_list[atom1]
|
|
751
|
+
norm_vec = np.linalg.norm(vec)
|
|
752
|
+
unit_vec = vec / norm_vec
|
|
753
|
+
dist_diff = distance - norm_vec
|
|
754
|
+
|
|
755
|
+
geom_num_list[atom1] = geom_num_list[atom1] - dist_diff * unit_vec * 0.5
|
|
756
|
+
geom_num_list[atom2] = geom_num_list[atom2] + dist_diff * unit_vec * 0.5
|
|
757
|
+
return geom_num_list
|
|
758
|
+
|
|
759
|
+
def change_fragm_distance_both_side(geom_num_list, fragm1, fragm2, distance):
|
|
760
|
+
center_1 = np.array([0.0, 0.0, 0.0], dtype="float64")
|
|
761
|
+
for i in fragm1:
|
|
762
|
+
center_1 += geom_num_list[i]
|
|
763
|
+
center_1 /= len(fragm1)
|
|
764
|
+
|
|
765
|
+
center_2 = np.array([0.0, 0.0, 0.0], dtype="float64")
|
|
766
|
+
for i in fragm2:
|
|
767
|
+
center_2 += geom_num_list[i]
|
|
768
|
+
center_2 /= len(fragm2)
|
|
769
|
+
|
|
770
|
+
vec = center_2 - center_1
|
|
771
|
+
norm_vec = np.linalg.norm(vec)
|
|
772
|
+
unit_vec = vec / norm_vec
|
|
773
|
+
dist_diff = distance - norm_vec
|
|
774
|
+
|
|
775
|
+
for i in fragm1:
|
|
776
|
+
geom_num_list[i] = geom_num_list[i] - dist_diff * unit_vec * 0.5
|
|
777
|
+
|
|
778
|
+
for i in fragm2:
|
|
779
|
+
geom_num_list[i] = geom_num_list[i] + dist_diff * unit_vec * 0.5
|
|
780
|
+
|
|
781
|
+
return geom_num_list
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def calc_bond_matrix(geom_num_list, element_list, threshold=1.2):
|
|
785
|
+
bond_matrix = np.zeros((len(geom_num_list), len(geom_num_list)))
|
|
786
|
+
|
|
787
|
+
for i in range(len(element_list)):
|
|
788
|
+
for j in range(i+1, len(element_list)):
|
|
789
|
+
r = np.linalg.norm(geom_num_list[i], geom_num_list[j])
|
|
790
|
+
r_cov = covalent_radii_lib(element_list[i]) + covalent_radii_lib(element_list[j])
|
|
791
|
+
if r < threshold * r_cov:
|
|
792
|
+
bond_matrix[i][j] = 1
|
|
793
|
+
bond_matrix[j][i] = 1
|
|
794
|
+
|
|
795
|
+
return bond_matrix
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def calc_RMS(data):
|
|
799
|
+
return np.sqrt(np.mean(data ** 2))
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def torch_rotate_around_axis(theta, axis='z'):
|
|
803
|
+
cos_theta = torch.cos(theta).reshape(1)
|
|
804
|
+
sin_theta = torch.sin(theta).reshape(1)
|
|
805
|
+
tensor_zero = torch.tensor([0.0], dtype=torch.float64, requires_grad=True)
|
|
806
|
+
tensor_one = torch.tensor([1.0], dtype=torch.float64, requires_grad=True)
|
|
807
|
+
|
|
808
|
+
if axis == 'x':
|
|
809
|
+
|
|
810
|
+
R = torch.stack([torch.cat([tensor_one, tensor_zero, tensor_zero]),
|
|
811
|
+
torch.cat([tensor_zero, cos_theta, -sin_theta]),
|
|
812
|
+
torch.cat([tensor_zero, sin_theta, cos_theta])])
|
|
813
|
+
elif axis == 'y':
|
|
814
|
+
|
|
815
|
+
R = torch.stack([torch.cat([cos_theta, tensor_zero, sin_theta]),
|
|
816
|
+
torch.cat([tensor_zero, tensor_one, tensor_zero]),
|
|
817
|
+
torch.cat([-sin_theta, tensor_zero, cos_theta])])
|
|
818
|
+
elif axis == 'z':
|
|
819
|
+
|
|
820
|
+
R = torch.stack([torch.cat([cos_theta, -sin_theta, tensor_zero]),
|
|
821
|
+
torch.cat([sin_theta, cos_theta, tensor_zero]),
|
|
822
|
+
torch.cat([tensor_zero, tensor_zero, tensor_one])])
|
|
823
|
+
else:
|
|
824
|
+
raise ValueError
|
|
825
|
+
|
|
826
|
+
return R
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def torch_align_vector_with_z(v):
|
|
830
|
+
v = v / torch.linalg.norm(v)
|
|
831
|
+
z = torch.tensor([0.0, 0.0, 1.0], dtype=v.dtype, requires_grad=True)
|
|
832
|
+
tensor_zero = torch.tensor([0.0], dtype=v.dtype, requires_grad=True)
|
|
833
|
+
axis = torch.linalg.cross(v, z)
|
|
834
|
+
axis_len = torch.linalg.norm(axis)
|
|
835
|
+
|
|
836
|
+
cos_theta = torch.dot(v, z)
|
|
837
|
+
sin_theta = axis_len
|
|
838
|
+
|
|
839
|
+
axis = axis / axis_len
|
|
840
|
+
axis = axis.reshape(3, 1)
|
|
841
|
+
K = torch.stack([
|
|
842
|
+
torch.cat([tensor_zero, -axis[2], axis[1]]),
|
|
843
|
+
torch.cat([axis[2], tensor_zero, -axis[0]]),
|
|
844
|
+
torch.cat([-axis[1], axis[0], tensor_zero])
|
|
845
|
+
])
|
|
846
|
+
|
|
847
|
+
R = torch.eye(3, dtype=v.dtype, requires_grad=True) + sin_theta * K + (1 - cos_theta) * torch.matmul(K, K)
|
|
848
|
+
return R
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def calc_path_length_list(geometry_list):
|
|
853
|
+
"""Calculate path length list for geometry distribution"""
|
|
854
|
+
path_length_list = [0.0]
|
|
855
|
+
for i in range(len(geometry_list)-1):
|
|
856
|
+
tmp_geometry_list_j = geometry_list[i+1] - np.mean(geometry_list[i+1], axis=0)
|
|
857
|
+
tmp_geometry_list_i = geometry_list[i] - np.mean(geometry_list[i], axis=0)
|
|
858
|
+
|
|
859
|
+
path_length = path_length_list[-1] + np.linalg.norm(tmp_geometry_list_j - tmp_geometry_list_i)
|
|
860
|
+
path_length_list.append(path_length)
|
|
861
|
+
return path_length_list
|
|
862
|
+
|
|
863
|
+
|
|
864
|
+
def apply_climbing_image(geometry_list, energy_list, element_list):
|
|
865
|
+
"""Apply climbing image method to locate transition states"""
|
|
866
|
+
path_length_list = calc_path_length_list(geometry_list)
|
|
867
|
+
total_length = path_length_list[-1]
|
|
868
|
+
local_maxima, local_minima = spline_interpolation(path_length_list, energy_list)
|
|
869
|
+
print(local_maxima)
|
|
870
|
+
|
|
871
|
+
for distance, energy in local_maxima:
|
|
872
|
+
print("Local maximum at distance: ", distance)
|
|
873
|
+
for i in range(2, len(path_length_list)-2):
|
|
874
|
+
if path_length_list[i] >= distance or distance >= path_length_list[i+1]:
|
|
875
|
+
continue
|
|
876
|
+
delta_t = (distance - path_length_list[i]) / (path_length_list[i+1] - path_length_list[i])
|
|
877
|
+
tmp_geometry = geometry_list[i] + (geometry_list[i+1] - geometry_list[i]) * delta_t
|
|
878
|
+
tmp_geom_list = [geometry_list[i], tmp_geometry, geometry_list[i+1]]
|
|
879
|
+
idpp_instance = IDPP()
|
|
880
|
+
tmp_geom_list = idpp_instance.opt_path(tmp_geom_list, element_list)
|
|
881
|
+
geometry_list[i] = tmp_geom_list[1]
|
|
882
|
+
return geometry_list
|
|
883
|
+
|
|
884
|
+
|