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
multioptpy/irc.py
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import glob
|
|
4
|
+
import csv
|
|
5
|
+
|
|
6
|
+
from multioptpy.Potential.potential import BiasPotentialCalculation
|
|
7
|
+
from multioptpy.Parameters.parameter import atomic_mass
|
|
8
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
9
|
+
from multioptpy.IRC.lqa import LQA
|
|
10
|
+
from multioptpy.IRC.hpc import HPC
|
|
11
|
+
from multioptpy.IRC.rk4 import RK4
|
|
12
|
+
from multioptpy.IRC.dvv import DVV
|
|
13
|
+
from multioptpy.IRC.modekill import ModeKill
|
|
14
|
+
from multioptpy.IRC.euler import Euler
|
|
15
|
+
from multioptpy.IRC.converge_criteria import convergence_check
|
|
16
|
+
from multioptpy.fileio import traj2list
|
|
17
|
+
|
|
18
|
+
### I recommend to use LQA method to calculate IRC path ###
|
|
19
|
+
|
|
20
|
+
class IRC:
|
|
21
|
+
"""Main class for Intrinsic Reaction Coordinate calculations
|
|
22
|
+
|
|
23
|
+
This class handles saddle point verification, forward/backward IRC calculations
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, directory, final_directory, irc_method, QM_interface, element_list,
|
|
27
|
+
electric_charge_and_multiplicity, force_data, xtb_method, FC_count=-1, hessian=None):
|
|
28
|
+
"""Initialize IRC calculator
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
directory : str
|
|
33
|
+
Working directory
|
|
34
|
+
final_directory : str
|
|
35
|
+
Directory for final output
|
|
36
|
+
irc_method : list
|
|
37
|
+
[step_size, max_step, method_name]
|
|
38
|
+
QM_interface : object
|
|
39
|
+
Interface to quantum mechanical calculator
|
|
40
|
+
element_list : list
|
|
41
|
+
List of atomic elements
|
|
42
|
+
electric_charge_and_multiplicity : tuple
|
|
43
|
+
Charge and multiplicity for the system
|
|
44
|
+
force_data : dict
|
|
45
|
+
Force field data for bias potential
|
|
46
|
+
xtb_method : str
|
|
47
|
+
XTB method specification
|
|
48
|
+
FC_count : int, optional
|
|
49
|
+
Frequency of full hessian recalculation, default=-1
|
|
50
|
+
hessian : numpy.ndarray, optional
|
|
51
|
+
Initial hessian matrix, default=None
|
|
52
|
+
"""
|
|
53
|
+
if hessian is None:
|
|
54
|
+
self.hessian_flag = False
|
|
55
|
+
else:
|
|
56
|
+
self.hessian_flag = True
|
|
57
|
+
self.hessian = hessian
|
|
58
|
+
|
|
59
|
+
self.step_size = float(irc_method[0])
|
|
60
|
+
self.max_step = int(irc_method[1])
|
|
61
|
+
self.method = str(irc_method[2])
|
|
62
|
+
tmp_method = irc_method[2].split(":")
|
|
63
|
+
if len(tmp_method) > 1:
|
|
64
|
+
self.method = tmp_method[0]
|
|
65
|
+
self.method_options = tmp_method[1:]
|
|
66
|
+
else:
|
|
67
|
+
self.method = irc_method[2]
|
|
68
|
+
self.method_options = []
|
|
69
|
+
|
|
70
|
+
self.file_directory = os.path.abspath(directory)+"/"
|
|
71
|
+
self.final_directory = os.path.abspath(final_directory)+"/"
|
|
72
|
+
self.QM_interface = QM_interface
|
|
73
|
+
|
|
74
|
+
self.element_list = element_list
|
|
75
|
+
self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
|
|
76
|
+
self.xtb_method = xtb_method
|
|
77
|
+
|
|
78
|
+
self.force_data = force_data
|
|
79
|
+
self.FC_count = FC_count
|
|
80
|
+
|
|
81
|
+
# convergence criteria
|
|
82
|
+
self.MAX_FORCE_THRESHOLD = 0.0004
|
|
83
|
+
self.RMS_FORCE_THRESHOLD = 0.0001
|
|
84
|
+
|
|
85
|
+
# Will be set in saddle_check
|
|
86
|
+
self.IRC_flag = False
|
|
87
|
+
self.initial_step = None
|
|
88
|
+
self.geom_num_list = None
|
|
89
|
+
self.ts_coords = None
|
|
90
|
+
self.fin_xyz_base = None # Added
|
|
91
|
+
self.terminal_struct_paths = [] # Added
|
|
92
|
+
|
|
93
|
+
def saddle_check(self):
|
|
94
|
+
"""Check if starting point is a saddle point and calculate initial displacement
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
numpy.ndarray
|
|
99
|
+
Initial displacement vector
|
|
100
|
+
bool
|
|
101
|
+
True if valid IRC starting point (has 1 imaginary mode)
|
|
102
|
+
numpy.ndarray
|
|
103
|
+
Current geometry coordinates
|
|
104
|
+
bool
|
|
105
|
+
True if calculation failed
|
|
106
|
+
"""
|
|
107
|
+
# Setup hessian calculation
|
|
108
|
+
if not self.hessian_flag:
|
|
109
|
+
self.QM_interface.hessian_flag = True
|
|
110
|
+
iter = 1
|
|
111
|
+
else:
|
|
112
|
+
self.QM_interface.FC_COUNT = -1
|
|
113
|
+
iter = 1
|
|
114
|
+
|
|
115
|
+
# Read input geometry
|
|
116
|
+
fin_xyz = glob.glob(self.final_directory+"/*.xyz")
|
|
117
|
+
# --- Added ---
|
|
118
|
+
if fin_xyz:
|
|
119
|
+
self.fin_xyz_base = os.path.basename(fin_xyz[0]).split('.')[0]
|
|
120
|
+
else:
|
|
121
|
+
print("Warning: No XYZ file found in final_directory. Using default name 'input' for terminal structures.")
|
|
122
|
+
self.fin_xyz_base = "input"
|
|
123
|
+
# --- End of addition ---
|
|
124
|
+
|
|
125
|
+
with open(fin_xyz[0], "r") as f:
|
|
126
|
+
words = f.read().splitlines()
|
|
127
|
+
geom_num_list = []
|
|
128
|
+
for i in range(len(words)):
|
|
129
|
+
if len(words[i].split()) > 3:
|
|
130
|
+
geom_num_list.append([
|
|
131
|
+
float(words[i].split()[1]),
|
|
132
|
+
float(words[i].split()[2]),
|
|
133
|
+
float(words[i].split()[3])
|
|
134
|
+
])
|
|
135
|
+
geom_num_list = np.array(geom_num_list)
|
|
136
|
+
# Calculate energy, gradient and hessian
|
|
137
|
+
init_e, init_g, geom_num_list, finish_frag = self.QM_interface.single_point(
|
|
138
|
+
self.final_directory,
|
|
139
|
+
self.element_list,
|
|
140
|
+
iter,
|
|
141
|
+
self.electric_charge_and_multiplicity,
|
|
142
|
+
self.xtb_method,
|
|
143
|
+
geom_num_list
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Reset QM interface settings
|
|
147
|
+
self.QM_interface.hessian_flag = False
|
|
148
|
+
self.QM_interface.FC_COUNT = self.FC_count
|
|
149
|
+
|
|
150
|
+
if finish_frag:
|
|
151
|
+
return 0, 0, 0, finish_frag
|
|
152
|
+
|
|
153
|
+
# Get hessian from QM calculation
|
|
154
|
+
self.hessian = self.QM_interface.Model_hess
|
|
155
|
+
self.QM_interface.hessian_flag = False
|
|
156
|
+
|
|
157
|
+
# Calculate bias potential
|
|
158
|
+
CalcBiaspot = BiasPotentialCalculation(self.final_directory)
|
|
159
|
+
_, init_B_e, init_B_g, BPA_hessian = CalcBiaspot.main(
|
|
160
|
+
init_e, init_g, geom_num_list, self.element_list,
|
|
161
|
+
self.force_data, init_g, 0, geom_num_list
|
|
162
|
+
)
|
|
163
|
+
isconverged = convergence_check(init_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD)
|
|
164
|
+
# Add bias potential hessian
|
|
165
|
+
self.hessian += BPA_hessian
|
|
166
|
+
|
|
167
|
+
# Project out translational and rotational modes
|
|
168
|
+
self.hessian = Calculationtools().project_out_hess_tr_and_rot(
|
|
169
|
+
self.hessian, self.element_list, geom_num_list
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Store initial state
|
|
173
|
+
self.init_e = init_e
|
|
174
|
+
self.init_g = init_g
|
|
175
|
+
self.init_B_e = init_B_e
|
|
176
|
+
self.init_B_g = init_B_g
|
|
177
|
+
|
|
178
|
+
# Find imaginary modes (negative eigenvalues)
|
|
179
|
+
eigenvalues, eigenvectors = np.linalg.eigh(self.hessian)
|
|
180
|
+
neg_indices = np.where(eigenvalues < -1e-8)[0]
|
|
181
|
+
imaginary_count = len(neg_indices)
|
|
182
|
+
print("Number of imaginary eigenvalues: ", imaginary_count)
|
|
183
|
+
|
|
184
|
+
# Determine initial step direction
|
|
185
|
+
if self.method.upper() == "MODEKILL":
|
|
186
|
+
print("Execute ModeKill")
|
|
187
|
+
# ModeKill: Remove imaginary modes using ModeKill class
|
|
188
|
+
IRC_flag = False
|
|
189
|
+
gradient = self.QM_interface.gradient.reshape(len(geom_num_list), 3)
|
|
190
|
+
initial_step = np.zeros_like(gradient)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
elif imaginary_count == 1 and isconverged:
|
|
194
|
+
print("Execute IRC")
|
|
195
|
+
# True IRC: Use transition vector (imaginary mode)
|
|
196
|
+
imaginary_idx = neg_indices[0]
|
|
197
|
+
transition_vector = eigenvectors[:, imaginary_idx].reshape(len(geom_num_list), 3)
|
|
198
|
+
initial_step = transition_vector / np.linalg.norm(transition_vector.flatten()) * self.step_size * 0.1
|
|
199
|
+
IRC_flag = True
|
|
200
|
+
else:
|
|
201
|
+
print("Execute meta-IRC")
|
|
202
|
+
# Meta-IRC: Use gradient direction
|
|
203
|
+
gradient = self.QM_interface.gradient.reshape(len(geom_num_list), 3)
|
|
204
|
+
initial_step = gradient / np.linalg.norm(gradient.flatten()) * self.step_size * 0.1
|
|
205
|
+
|
|
206
|
+
# Mass-weight the initial step for meta-IRC
|
|
207
|
+
sqrt_mass_list = np.sqrt([atomic_mass(elem) for elem in self.element_list])
|
|
208
|
+
for i in range(len(initial_step)):
|
|
209
|
+
initial_step[i] /= sqrt_mass_list[i]
|
|
210
|
+
|
|
211
|
+
IRC_flag = False
|
|
212
|
+
|
|
213
|
+
return initial_step, IRC_flag, geom_num_list, finish_frag
|
|
214
|
+
|
|
215
|
+
# --- New method added ---
|
|
216
|
+
def write_xyz(self, filename, geometry):
|
|
217
|
+
"""Write a single geometry to an XYZ file"""
|
|
218
|
+
with open(filename, 'w') as outfile:
|
|
219
|
+
outfile.write(f"{len(geometry)}\n")
|
|
220
|
+
outfile.write("Terminal structure\n") # Comment line
|
|
221
|
+
for i in range(len(geometry)):
|
|
222
|
+
outfile.write(f"{self.element_list[i]} {' '.join(map(str, geometry[i]))}\n")
|
|
223
|
+
# --- End of new method ---
|
|
224
|
+
|
|
225
|
+
def _get_irc_method_class(self):
|
|
226
|
+
"""Get the appropriate IRC method class based on method name
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
class
|
|
231
|
+
IRC method class (LQA, RK4, ModeKill, Euler, or DVV)
|
|
232
|
+
"""
|
|
233
|
+
method_map = {
|
|
234
|
+
"LQA": LQA,
|
|
235
|
+
"RK4": RK4,
|
|
236
|
+
"MODEKILL": ModeKill,
|
|
237
|
+
"EULER": Euler,
|
|
238
|
+
"DVV": DVV,
|
|
239
|
+
"HPC": HPC
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
method_key = self.method.upper()
|
|
243
|
+
# Default to LQA if method is not recognized
|
|
244
|
+
if method_key not in method_map:
|
|
245
|
+
print(f"Unexpected method '{self.method}'. (default method is LQA.)")
|
|
246
|
+
return LQA
|
|
247
|
+
|
|
248
|
+
return method_map[method_key]
|
|
249
|
+
|
|
250
|
+
def _run_single_irc(self, directory, initial_geometry):
|
|
251
|
+
"""Run a single IRC calculation
|
|
252
|
+
|
|
253
|
+
Parameters
|
|
254
|
+
----------
|
|
255
|
+
directory : str
|
|
256
|
+
Directory to store results
|
|
257
|
+
initial_geometry : numpy.ndarray
|
|
258
|
+
Initial geometry coordinates
|
|
259
|
+
|
|
260
|
+
Returns
|
|
261
|
+
-------
|
|
262
|
+
object
|
|
263
|
+
IRC method instance
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
MethodClass = self._get_irc_method_class()
|
|
268
|
+
|
|
269
|
+
kill_inds = None # Initialize kill_inds
|
|
270
|
+
if len(self.method_options) > 0 and MethodClass == ModeKill:
|
|
271
|
+
# Pass kill_inds if specified in method options
|
|
272
|
+
try:
|
|
273
|
+
kill_inds = [int(idx) for idx in self.method_options[0].split(",")]
|
|
274
|
+
print(f"Using specified kill_inds: {kill_inds}")
|
|
275
|
+
except ValueError:
|
|
276
|
+
print("Invalid kill_inds format. It should be a comma-separated list of integers.")
|
|
277
|
+
kill_inds = None
|
|
278
|
+
|
|
279
|
+
irc_instance = MethodClass(
|
|
280
|
+
self.element_list,
|
|
281
|
+
self.electric_charge_and_multiplicity,
|
|
282
|
+
self.FC_count,
|
|
283
|
+
directory,
|
|
284
|
+
self.final_directory,
|
|
285
|
+
self.force_data,
|
|
286
|
+
max_step=self.max_step,
|
|
287
|
+
step_size=self.step_size,
|
|
288
|
+
init_coord=initial_geometry,
|
|
289
|
+
init_hess=self.hessian,
|
|
290
|
+
calc_engine=self.QM_interface,
|
|
291
|
+
xtb_method=self.xtb_method,
|
|
292
|
+
kill_inds=kill_inds
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
irc_instance.run()
|
|
296
|
+
return irc_instance
|
|
297
|
+
|
|
298
|
+
def _run_irc(self):
|
|
299
|
+
"""Run IRC calculation in both forward and backward directions"""
|
|
300
|
+
# Forward direction (from TS to products)
|
|
301
|
+
print("Forward IRC")
|
|
302
|
+
init_geom = self.geom_num_list + self.initial_step
|
|
303
|
+
|
|
304
|
+
# Create forward direction directory
|
|
305
|
+
fwd_dir = os.path.join(self.file_directory, "irc_forward")
|
|
306
|
+
os.makedirs(fwd_dir, exist_ok=True)
|
|
307
|
+
|
|
308
|
+
# Run forward IRC
|
|
309
|
+
self._run_single_irc(fwd_dir, init_geom)
|
|
310
|
+
|
|
311
|
+
# Backward direction (from TS to reactants)
|
|
312
|
+
print("Backward IRC")
|
|
313
|
+
init_geom = self.geom_num_list - self.initial_step
|
|
314
|
+
|
|
315
|
+
# Create backward direction directory
|
|
316
|
+
bwd_dir = os.path.join(self.file_directory, "irc_backward")
|
|
317
|
+
os.makedirs(bwd_dir, exist_ok=True)
|
|
318
|
+
|
|
319
|
+
# Run backward IRC
|
|
320
|
+
self._run_single_irc(bwd_dir, init_geom)
|
|
321
|
+
|
|
322
|
+
# Combine XYZ files from forward and backward directions
|
|
323
|
+
self.combine_xyz_files(fwd_dir, bwd_dir)
|
|
324
|
+
|
|
325
|
+
# Combine forward and backward CSV data into a single file
|
|
326
|
+
self.combine_csv_data(fwd_dir, bwd_dir)
|
|
327
|
+
|
|
328
|
+
def _run_meta_irc(self):
|
|
329
|
+
"""Run meta-IRC calculation in a single direction"""
|
|
330
|
+
init_geom = self.geom_num_list - self.initial_step
|
|
331
|
+
self._run_single_irc(self.file_directory, init_geom)
|
|
332
|
+
|
|
333
|
+
def calc_IRCpath(self):
|
|
334
|
+
"""Calculate IRC path in forward and/or backward directions"""
|
|
335
|
+
print("IRC carry out...")
|
|
336
|
+
|
|
337
|
+
if self.IRC_flag:
|
|
338
|
+
self._run_irc()
|
|
339
|
+
else:
|
|
340
|
+
self._run_meta_irc()
|
|
341
|
+
# --- Added for terminal structure output (meta-IRC) ---
|
|
342
|
+
meta_irc_xyz_file = os.path.join(self.file_directory, "irc_structures.xyz")
|
|
343
|
+
if os.path.exists(meta_irc_xyz_file):
|
|
344
|
+
try:
|
|
345
|
+
meta_irc_geometry_list, _, _ = traj2list(meta_irc_xyz_file, [0, 1])
|
|
346
|
+
if meta_irc_geometry_list: # Check if list is not empty
|
|
347
|
+
terminal_geom_meta = meta_irc_geometry_list[-1]
|
|
348
|
+
outfile_name_meta = os.path.join(self.file_directory, f"{self.fin_xyz_base}_irc_endpoint_1.xyz")
|
|
349
|
+
self.write_xyz(outfile_name_meta, terminal_geom_meta)
|
|
350
|
+
self.terminal_struct_paths = [os.path.abspath(outfile_name_meta)]
|
|
351
|
+
print(f"Meta-IRC terminal structure saved to {outfile_name_meta}")
|
|
352
|
+
except Exception as e:
|
|
353
|
+
print(f"Error processing meta-IRC terminal structure: {e}")
|
|
354
|
+
# --- End of addition ---
|
|
355
|
+
|
|
356
|
+
return
|
|
357
|
+
|
|
358
|
+
def combine_csv_data(self, fwd_dir, bwd_dir):
|
|
359
|
+
"""Combine forward and backward CSV data into a single file
|
|
360
|
+
|
|
361
|
+
Parameters
|
|
362
|
+
----------
|
|
363
|
+
fwd_dir : str
|
|
364
|
+
Forward directory path
|
|
365
|
+
bwd_dir : str
|
|
366
|
+
Backward directory path
|
|
367
|
+
"""
|
|
368
|
+
# Final CSV file
|
|
369
|
+
combined_csv = os.path.join(self.file_directory, "irc_combined_data.csv")
|
|
370
|
+
|
|
371
|
+
# Read backward data (to be reversed)
|
|
372
|
+
bwd_data = []
|
|
373
|
+
bwd_csv_path = os.path.join(bwd_dir, "irc_energies_gradients.csv")
|
|
374
|
+
if os.path.exists(bwd_csv_path):
|
|
375
|
+
with open(bwd_csv_path, 'r') as csvfile:
|
|
376
|
+
reader = csv.reader(csvfile)
|
|
377
|
+
try:
|
|
378
|
+
next(reader) # Skip header
|
|
379
|
+
for row in reader:
|
|
380
|
+
bwd_data.append(row)
|
|
381
|
+
except StopIteration:
|
|
382
|
+
print("Warning: Backward CSV file is empty.")
|
|
383
|
+
else:
|
|
384
|
+
print(f"Warning: Backward CSV file not found at {bwd_csv_path}")
|
|
385
|
+
|
|
386
|
+
# Read forward data
|
|
387
|
+
fwd_data = []
|
|
388
|
+
fwd_csv_path = os.path.join(fwd_dir, "irc_energies_gradients.csv")
|
|
389
|
+
if os.path.exists(fwd_csv_path):
|
|
390
|
+
with open(fwd_csv_path, 'r') as csvfile:
|
|
391
|
+
reader = csv.reader(csvfile)
|
|
392
|
+
try:
|
|
393
|
+
next(reader) # Skip header
|
|
394
|
+
for row in reader:
|
|
395
|
+
fwd_data.append(row)
|
|
396
|
+
except StopIteration:
|
|
397
|
+
print("Warning: Forward CSV file is empty.")
|
|
398
|
+
else:
|
|
399
|
+
print(f"Warning: Forward CSV file not found at {fwd_csv_path}")
|
|
400
|
+
|
|
401
|
+
# Prepare TS point data
|
|
402
|
+
ts_data = [0, self.init_e, self.init_B_e, np.sqrt((self.init_g**2).mean()), np.sqrt((self.init_B_g**2).mean())]
|
|
403
|
+
|
|
404
|
+
# Write combined data
|
|
405
|
+
with open(combined_csv, 'w', newline='') as csvfile:
|
|
406
|
+
writer = csv.writer(csvfile)
|
|
407
|
+
writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)', 'RMS Gradient', 'RMS Bias Gradient'])
|
|
408
|
+
|
|
409
|
+
# Write reversed backward data with negative step numbers
|
|
410
|
+
for i, row in enumerate(reversed(bwd_data)):
|
|
411
|
+
try:
|
|
412
|
+
step = -int(row[0])
|
|
413
|
+
writer.writerow([step, row[1], row[2], row[3], row[4]])
|
|
414
|
+
except (IndexError, ValueError) as e:
|
|
415
|
+
print(f"Warning: Skipping malformed row in backward CSV data: {row} ({e})")
|
|
416
|
+
|
|
417
|
+
# Write TS point (step 0)
|
|
418
|
+
writer.writerow(ts_data)
|
|
419
|
+
|
|
420
|
+
# Write forward data with positive step numbers
|
|
421
|
+
for row in fwd_data:
|
|
422
|
+
try:
|
|
423
|
+
writer.writerow(row)
|
|
424
|
+
except (IndexError, ValueError) as e:
|
|
425
|
+
print(f"Warning: Skipping malformed row in forward CSV data: {row} ({e})")
|
|
426
|
+
|
|
427
|
+
print(f"Combined IRC data saved to {combined_csv}")
|
|
428
|
+
|
|
429
|
+
def combine_xyz_files(self, fwd_dir, bwd_dir):
|
|
430
|
+
"""Combine forward and backward XYZ structures into a single XYZ file
|
|
431
|
+
and output terminal structures.
|
|
432
|
+
|
|
433
|
+
Parameters
|
|
434
|
+
----------
|
|
435
|
+
fwd_dir : str
|
|
436
|
+
Forward directory path
|
|
437
|
+
bwd_dir : str
|
|
438
|
+
Backward directory path
|
|
439
|
+
"""
|
|
440
|
+
# Path for combined XYZ file
|
|
441
|
+
combined_xyz = os.path.join(self.file_directory, "irc_combined_path.xyz")
|
|
442
|
+
|
|
443
|
+
# Find all XYZ files in forward and backward directories
|
|
444
|
+
fwd_xyz_file = os.path.join(fwd_dir, "irc_structures.xyz")
|
|
445
|
+
bwd_xyz_file = os.path.join(bwd_dir, "irc_structures.xyz")
|
|
446
|
+
|
|
447
|
+
fwd_irc_geometry_list = []
|
|
448
|
+
if os.path.exists(fwd_xyz_file):
|
|
449
|
+
try:
|
|
450
|
+
fwd_irc_geometry_list, _, _ = traj2list(fwd_xyz_file, [0, 1])
|
|
451
|
+
except Exception as e:
|
|
452
|
+
print(f"Error reading forward XYZ file: {e}")
|
|
453
|
+
else:
|
|
454
|
+
print(f"Warning: Forward XYZ file not found at {fwd_xyz_file}")
|
|
455
|
+
|
|
456
|
+
bwd_irc_geometry_list = []
|
|
457
|
+
if os.path.exists(bwd_xyz_file):
|
|
458
|
+
try:
|
|
459
|
+
bwd_irc_geometry_list, _, _ = traj2list(bwd_xyz_file, [0, 1])
|
|
460
|
+
except Exception as e:
|
|
461
|
+
print(f"Error reading backward XYZ file: {e}")
|
|
462
|
+
else:
|
|
463
|
+
print(f"Warning: Backward XYZ file not found at {bwd_xyz_file}")
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
self.terminal_struct_paths = [] # Clear any previous paths
|
|
468
|
+
if fwd_irc_geometry_list: # Check if list is not empty
|
|
469
|
+
terminal_geom_fwd = fwd_irc_geometry_list[-1]
|
|
470
|
+
outfile_name_fwd = os.path.join(self.file_directory, f"{self.fin_xyz_base}_irc_endpoint_1.xyz")
|
|
471
|
+
self.write_xyz(outfile_name_fwd, terminal_geom_fwd)
|
|
472
|
+
self.terminal_struct_paths.append(os.path.abspath(outfile_name_fwd))
|
|
473
|
+
print(f"Forward terminal structure saved to {outfile_name_fwd}")
|
|
474
|
+
|
|
475
|
+
if bwd_irc_geometry_list: # Check if list is not empty
|
|
476
|
+
terminal_geom_bwd = bwd_irc_geometry_list[-1]
|
|
477
|
+
outfile_name_bwd = os.path.join(self.file_directory, f"{self.fin_xyz_base}_irc_endpoint_2.xyz")
|
|
478
|
+
self.write_xyz(outfile_name_bwd, terminal_geom_bwd)
|
|
479
|
+
self.terminal_struct_paths.append(os.path.abspath(outfile_name_bwd))
|
|
480
|
+
print(f"Backward terminal structure saved to {outfile_name_bwd}")
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
fwd_irc_geometry_list = fwd_irc_geometry_list[::-1] # Reverse forward list
|
|
484
|
+
|
|
485
|
+
# TS structure from the final directory
|
|
486
|
+
ts_xyz_file_path = glob.glob(os.path.join(self.final_directory, "*.xyz"))
|
|
487
|
+
|
|
488
|
+
# Write combined XYZ file
|
|
489
|
+
with open(combined_xyz, 'w') as outfile:
|
|
490
|
+
# Forward structures (from last to first)
|
|
491
|
+
for xyz in fwd_irc_geometry_list:
|
|
492
|
+
outfile.write(f"{len(xyz)}\n")
|
|
493
|
+
outfile.write("\n")
|
|
494
|
+
for i in range(len(xyz)):
|
|
495
|
+
outfile.write(self.element_list[i] + " " + " ".join(map(str, xyz[i])) + "\n")
|
|
496
|
+
|
|
497
|
+
# TS structure
|
|
498
|
+
if ts_xyz_file_path:
|
|
499
|
+
with open(ts_xyz_file_path[0], 'r') as infile:
|
|
500
|
+
outfile.write(infile.read())
|
|
501
|
+
else:
|
|
502
|
+
print(f"Warning: TS XYZ file not found in {self.final_directory}")
|
|
503
|
+
|
|
504
|
+
# Backward structures (from first to last)
|
|
505
|
+
for xyz in bwd_irc_geometry_list:
|
|
506
|
+
outfile.write(f"{len(xyz)}\n")
|
|
507
|
+
outfile.write("\n")
|
|
508
|
+
for i in range(len(xyz)):
|
|
509
|
+
outfile.write(self.element_list[i] + " " + " ".join(map(str, xyz[i])) + "\n")
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
print(f"Combined IRC path saved to {combined_xyz}")
|
|
513
|
+
|
|
514
|
+
def run(self):
|
|
515
|
+
"""Main function to run IRC calculation"""
|
|
516
|
+
# Check if starting point is a saddle point and get initial displacement
|
|
517
|
+
self.initial_step, self.IRC_flag, self.geom_num_list, finish_flag = self.saddle_check()
|
|
518
|
+
|
|
519
|
+
if finish_flag:
|
|
520
|
+
print("IRC calculation failed.")
|
|
521
|
+
self.terminal_struct_paths = []
|
|
522
|
+
return
|
|
523
|
+
|
|
524
|
+
# Calculate the IRC path
|
|
525
|
+
self.calc_IRCpath()
|
|
526
|
+
|
|
527
|
+
print("IRC calculation is finished.")
|
|
528
|
+
# self.terminal_struct_paths can be accessed for terminal structure file paths.
|
|
529
|
+
return
|