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/rk4.py
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import os
|
|
3
|
+
import copy
|
|
4
|
+
import csv
|
|
5
|
+
|
|
6
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
7
|
+
from multioptpy.Optimizer.hessian_update import ModelHessianUpdate
|
|
8
|
+
from multioptpy.Potential.potential import BiasPotentialCalculation
|
|
9
|
+
from multioptpy.Parameters.parameter import UnitValueLib, atomic_mass
|
|
10
|
+
from multioptpy.IRC.converge_criteria import convergence_check
|
|
11
|
+
from multioptpy.Visualization.visualization import Graph
|
|
12
|
+
from multioptpy.PESAnalyzer.calc_irc_curvature import calc_irc_curvature_properties, save_curvature_properties_to_file
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class RK4:
|
|
16
|
+
"""Runge-Kutta 4th order method for IRC calculations
|
|
17
|
+
|
|
18
|
+
References
|
|
19
|
+
----------
|
|
20
|
+
[1] J. Chem. Phys. 95, 9, 6758–6763 (1991)
|
|
21
|
+
[2] Chem. Phys. Lett. 437, 1–3, 120-125 (2007)
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, element_list, electric_charge_and_multiplicity, FC_count, file_directory,
|
|
25
|
+
final_directory, force_data, max_step=1000, step_size=0.1, init_coord=None,
|
|
26
|
+
init_hess=None, calc_engine=None, xtb_method=None, **kwargs):
|
|
27
|
+
"""Initialize RK4 IRC calculator
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
element_list : list
|
|
32
|
+
List of atomic elements
|
|
33
|
+
electric_charge_and_multiplicity : tuple
|
|
34
|
+
Charge and multiplicity for the system
|
|
35
|
+
FC_count : int
|
|
36
|
+
Frequency of full hessian recalculation
|
|
37
|
+
file_directory : str
|
|
38
|
+
Working directory
|
|
39
|
+
final_directory : str
|
|
40
|
+
Directory for final output
|
|
41
|
+
force_data : dict
|
|
42
|
+
Force field data for bias potential
|
|
43
|
+
max_step : int, optional
|
|
44
|
+
Maximum number of steps
|
|
45
|
+
step_size : float, optional
|
|
46
|
+
Step size for the IRC
|
|
47
|
+
init_coord : numpy.ndarray, optional
|
|
48
|
+
Initial coordinates
|
|
49
|
+
init_hess : numpy.ndarray, optional
|
|
50
|
+
Initial hessian
|
|
51
|
+
calc_engine : object, optional
|
|
52
|
+
Calculator engine
|
|
53
|
+
xtb_method : str, optional
|
|
54
|
+
XTB method specification
|
|
55
|
+
"""
|
|
56
|
+
self.max_step = max_step
|
|
57
|
+
self.step_size = step_size
|
|
58
|
+
self.ModelHessianUpdate = ModelHessianUpdate()
|
|
59
|
+
self.CE = calc_engine
|
|
60
|
+
self.FC_count = FC_count
|
|
61
|
+
|
|
62
|
+
# initial condition
|
|
63
|
+
self.coords = init_coord
|
|
64
|
+
self.init_hess = init_hess
|
|
65
|
+
self.mw_hessian = init_hess # Mass-weighted hessian
|
|
66
|
+
self.xtb_method = xtb_method
|
|
67
|
+
|
|
68
|
+
# convergence criteria
|
|
69
|
+
self.MAX_FORCE_THRESHOLD = 0.0004
|
|
70
|
+
self.RMS_FORCE_THRESHOLD = 0.0001
|
|
71
|
+
|
|
72
|
+
self.element_list = element_list
|
|
73
|
+
self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
|
|
74
|
+
self.directory = file_directory
|
|
75
|
+
self.final_directory = final_directory
|
|
76
|
+
self.force_data = force_data
|
|
77
|
+
|
|
78
|
+
# IRC data storage for current calculation (needed for immediate operations)
|
|
79
|
+
# These will no longer store the full trajectory but only recent points needed for calculations
|
|
80
|
+
self.irc_bias_energy_list = []
|
|
81
|
+
self.irc_energy_list = []
|
|
82
|
+
self.irc_mw_coords = []
|
|
83
|
+
self.irc_mw_gradients = []
|
|
84
|
+
self.irc_mw_bias_gradients = []
|
|
85
|
+
self.path_bending_angle_list = []
|
|
86
|
+
|
|
87
|
+
# Create data files
|
|
88
|
+
self.create_csv_file()
|
|
89
|
+
self.create_xyz_file()
|
|
90
|
+
|
|
91
|
+
def create_csv_file(self):
|
|
92
|
+
"""Create CSV file for energy and gradient data"""
|
|
93
|
+
self.csv_filename = os.path.join(self.directory, "irc_energies_gradients.csv")
|
|
94
|
+
with open(self.csv_filename, 'w', newline='') as csvfile:
|
|
95
|
+
writer = csv.writer(csvfile)
|
|
96
|
+
writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)', 'RMS Gradient', 'RMS Bias Gradient'])
|
|
97
|
+
|
|
98
|
+
def create_xyz_file(self):
|
|
99
|
+
"""Create XYZ file for structure data"""
|
|
100
|
+
self.xyz_filename = os.path.join(self.directory, "irc_structures.xyz")
|
|
101
|
+
# Create empty file (will be appended to later)
|
|
102
|
+
open(self.xyz_filename, 'w').close()
|
|
103
|
+
|
|
104
|
+
def save_to_csv(self, step, energy, bias_energy, gradient, bias_gradient):
|
|
105
|
+
"""Save energy and gradient data to CSV file
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
step : int
|
|
110
|
+
Current step number
|
|
111
|
+
energy : float
|
|
112
|
+
Energy in Hartree
|
|
113
|
+
bias_energy : float
|
|
114
|
+
Bias energy in Hartree
|
|
115
|
+
gradient : numpy.ndarray
|
|
116
|
+
Gradient array
|
|
117
|
+
bias_gradient : numpy.ndarray
|
|
118
|
+
Bias gradient array
|
|
119
|
+
"""
|
|
120
|
+
rms_grad = np.sqrt((gradient**2).mean())
|
|
121
|
+
rms_bias_grad = np.sqrt((bias_gradient**2).mean())
|
|
122
|
+
|
|
123
|
+
with open(self.csv_filename, 'a', newline='') as csvfile:
|
|
124
|
+
writer = csv.writer(csvfile)
|
|
125
|
+
writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad])
|
|
126
|
+
|
|
127
|
+
def save_xyz_structure(self, step, coords):
|
|
128
|
+
"""Save molecular structure to XYZ file
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
step : int
|
|
133
|
+
Current step number
|
|
134
|
+
coords : numpy.ndarray
|
|
135
|
+
Atomic coordinates in Bohr
|
|
136
|
+
"""
|
|
137
|
+
# Convert coordinates to Angstroms
|
|
138
|
+
coords_angstrom = coords * UnitValueLib().bohr2angstroms
|
|
139
|
+
|
|
140
|
+
with open(self.xyz_filename, 'a') as f:
|
|
141
|
+
# Number of atoms and comment line
|
|
142
|
+
f.write(f"{len(coords)}\n")
|
|
143
|
+
f.write(f"IRC Step {step}\n")
|
|
144
|
+
|
|
145
|
+
# Atomic coordinates
|
|
146
|
+
for i, coord in enumerate(coords_angstrom):
|
|
147
|
+
f.write(f"{self.element_list[i]:<3} {coord[0]:15.10f} {coord[1]:15.10f} {coord[2]:15.10f}\n")
|
|
148
|
+
|
|
149
|
+
def get_mass_array(self):
|
|
150
|
+
"""Create arrays of atomic masses for mass-weighting operations"""
|
|
151
|
+
elem_mass_list = np.array([atomic_mass(elem) for elem in self.element_list], dtype="float64")
|
|
152
|
+
sqrt_mass_list = np.sqrt(elem_mass_list)
|
|
153
|
+
|
|
154
|
+
# Create arrays for 3D operations (x,y,z for each atom)
|
|
155
|
+
three_elem_mass_list = np.repeat(elem_mass_list, 3)
|
|
156
|
+
three_sqrt_mass_list = np.repeat(sqrt_mass_list, 3)
|
|
157
|
+
|
|
158
|
+
return elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list
|
|
159
|
+
|
|
160
|
+
def mass_weight_hessian(self, hessian, three_sqrt_mass_list):
|
|
161
|
+
"""Apply mass-weighting to the hessian matrix
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
hessian : numpy.ndarray
|
|
166
|
+
Hessian matrix in non-mass-weighted coordinates
|
|
167
|
+
three_sqrt_mass_list : numpy.ndarray
|
|
168
|
+
Array of sqrt(mass) values repeated for x,y,z per atom
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
numpy.ndarray
|
|
173
|
+
Mass-weighted hessian
|
|
174
|
+
"""
|
|
175
|
+
mass_mat = np.diag(1.0 / three_sqrt_mass_list)
|
|
176
|
+
return np.dot(mass_mat, np.dot(hessian, mass_mat))
|
|
177
|
+
|
|
178
|
+
def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
|
|
179
|
+
"""Convert coordinates to mass-weighted coordinates
|
|
180
|
+
|
|
181
|
+
Parameters
|
|
182
|
+
----------
|
|
183
|
+
coordinates : numpy.ndarray
|
|
184
|
+
Coordinates in non-mass-weighted form
|
|
185
|
+
sqrt_mass_list : numpy.ndarray
|
|
186
|
+
Array of sqrt(mass) values for each atom
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
numpy.ndarray
|
|
191
|
+
Mass-weighted coordinates
|
|
192
|
+
"""
|
|
193
|
+
mw_coords = copy.deepcopy(coordinates)
|
|
194
|
+
for i in range(len(coordinates)):
|
|
195
|
+
mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
|
|
196
|
+
return mw_coords
|
|
197
|
+
|
|
198
|
+
def mass_weight_gradient(self, gradient, sqrt_mass_list):
|
|
199
|
+
"""Convert gradient to mass-weighted form
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
gradient : numpy.ndarray
|
|
204
|
+
Gradient in non-mass-weighted form
|
|
205
|
+
sqrt_mass_list : numpy.ndarray
|
|
206
|
+
Array of sqrt(mass) values for each atom
|
|
207
|
+
|
|
208
|
+
Returns
|
|
209
|
+
-------
|
|
210
|
+
numpy.ndarray
|
|
211
|
+
Mass-weighted gradient
|
|
212
|
+
"""
|
|
213
|
+
mw_gradient = copy.deepcopy(gradient)
|
|
214
|
+
for i in range(len(gradient)):
|
|
215
|
+
mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
|
|
216
|
+
return mw_gradient
|
|
217
|
+
|
|
218
|
+
def unmass_weight_step(self, step, sqrt_mass_list):
|
|
219
|
+
"""Convert a step vector from mass-weighted to non-mass-weighted coordinates
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
step : numpy.ndarray
|
|
224
|
+
Step in mass-weighted form
|
|
225
|
+
sqrt_mass_list : numpy.ndarray
|
|
226
|
+
Array of sqrt(mass) values for each atom
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
numpy.ndarray
|
|
231
|
+
Step in non-mass-weighted coordinates
|
|
232
|
+
"""
|
|
233
|
+
unmw_step = copy.deepcopy(step)
|
|
234
|
+
for i in range(len(step)):
|
|
235
|
+
unmw_step[i] = step[i] / sqrt_mass_list[i]
|
|
236
|
+
return unmw_step
|
|
237
|
+
|
|
238
|
+
def check_energy_oscillation(self, energy_list):
|
|
239
|
+
"""Check if energy is oscillating (going up and down)
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
energy_list : list
|
|
244
|
+
List of energy values
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
bool
|
|
249
|
+
True if energy is oscillating, False otherwise
|
|
250
|
+
"""
|
|
251
|
+
if len(energy_list) < 3:
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
# Check if the energy changes direction (from increasing to decreasing or vice versa)
|
|
255
|
+
last_diff = energy_list[-1] - energy_list[-2]
|
|
256
|
+
prev_diff = energy_list[-2] - energy_list[-3]
|
|
257
|
+
|
|
258
|
+
# Return True if the energy direction has changed
|
|
259
|
+
return (last_diff * prev_diff) < 0
|
|
260
|
+
|
|
261
|
+
def get_k(self, mw_coords, mw_gradient):
|
|
262
|
+
"""Calculate k value for RK4 integration
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
mw_coords : numpy.ndarray
|
|
267
|
+
Mass-weighted coordinates
|
|
268
|
+
mw_gradient : numpy.ndarray
|
|
269
|
+
Mass-weighted gradient
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
numpy.ndarray
|
|
274
|
+
k vector for RK4 step
|
|
275
|
+
"""
|
|
276
|
+
# Flatten gradient for norm calculation
|
|
277
|
+
flat_gradient = mw_gradient.flatten()
|
|
278
|
+
norm = np.linalg.norm(flat_gradient)
|
|
279
|
+
|
|
280
|
+
# Avoid division by zero
|
|
281
|
+
if norm < 1e-12:
|
|
282
|
+
direction = np.zeros_like(mw_gradient)
|
|
283
|
+
else:
|
|
284
|
+
direction = -mw_gradient / norm
|
|
285
|
+
|
|
286
|
+
# Return step scaled by step_size
|
|
287
|
+
return self.step_size * direction
|
|
288
|
+
|
|
289
|
+
def step(self, mw_gradient, geom_num_list, mw_BPA_hessian, sqrt_mass_list):
|
|
290
|
+
"""Calculate a single RK4 IRC step
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
mw_gradient : numpy.ndarray
|
|
295
|
+
Mass-weighted gradient
|
|
296
|
+
geom_num_list : numpy.ndarray
|
|
297
|
+
Current geometry coordinates
|
|
298
|
+
mw_BPA_hessian : numpy.ndarray
|
|
299
|
+
Mass-weighted bias potential hessian
|
|
300
|
+
sqrt_mass_list : numpy.ndarray
|
|
301
|
+
Array of sqrt(mass) values for each atom
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
numpy.ndarray
|
|
306
|
+
New geometry coordinates
|
|
307
|
+
"""
|
|
308
|
+
# Update Hessian if we have previous points
|
|
309
|
+
if len(self.irc_mw_gradients) > 1 and len(self.irc_mw_coords) > 1:
|
|
310
|
+
delta_g = (self.irc_mw_gradients[-1] - self.irc_mw_gradients[-2]).reshape(-1, 1)
|
|
311
|
+
delta_x = (self.irc_mw_coords[-1] - self.irc_mw_coords[-2]).reshape(-1, 1)
|
|
312
|
+
|
|
313
|
+
# Only update if the step and gradient difference are meaningful
|
|
314
|
+
if np.dot(delta_x.T, delta_g)[0, 0] > 1e-10:
|
|
315
|
+
delta_hess = self.ModelHessianUpdate.BFGS_hessian_update(self.mw_hessian, delta_x, delta_g)
|
|
316
|
+
self.mw_hessian += delta_hess
|
|
317
|
+
|
|
318
|
+
# Add bias potential hessian to Hessian
|
|
319
|
+
combined_hessian = self.mw_hessian + mw_BPA_hessian
|
|
320
|
+
|
|
321
|
+
# Mass-weight the current coordinates
|
|
322
|
+
mw_coords = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
|
|
323
|
+
|
|
324
|
+
# Runge-Kutta 4th order integration
|
|
325
|
+
# k1 calculation
|
|
326
|
+
k1 = self.get_k(mw_coords, mw_gradient)
|
|
327
|
+
|
|
328
|
+
# k2 calculation - we need to evaluate gradient at coords + 0.5*k1
|
|
329
|
+
# This requires a new energy/gradient calculation at this point
|
|
330
|
+
mw_coords_k2 = mw_coords + 0.5 * k1
|
|
331
|
+
coords_k2 = self.unmass_weight_step(mw_coords_k2, sqrt_mass_list)
|
|
332
|
+
|
|
333
|
+
e_k2, g_k2, _, _ = self.CE.single_point(
|
|
334
|
+
self.final_directory,
|
|
335
|
+
self.element_list,
|
|
336
|
+
-1, # Temporary calculation, no step number
|
|
337
|
+
self.electric_charge_and_multiplicity,
|
|
338
|
+
self.xtb_method,
|
|
339
|
+
UnitValueLib().bohr2angstroms * coords_k2
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
# Calculate bias potential at k2 point
|
|
343
|
+
CalcBiaspot = BiasPotentialCalculation(self.directory)
|
|
344
|
+
_, _, B_g_k2, _ = CalcBiaspot.main(
|
|
345
|
+
e_k2, g_k2, coords_k2, self.element_list,
|
|
346
|
+
self.force_data, g_k2, -1, coords_k2
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Mass-weight k2 gradient
|
|
350
|
+
mw_B_g_k2 = self.mass_weight_gradient(B_g_k2, sqrt_mass_list)
|
|
351
|
+
k2 = self.get_k(mw_coords_k2, mw_B_g_k2)
|
|
352
|
+
|
|
353
|
+
# k3 calculation
|
|
354
|
+
mw_coords_k3 = mw_coords + 0.5 * k2
|
|
355
|
+
coords_k3 = self.unmass_weight_step(mw_coords_k3, sqrt_mass_list)
|
|
356
|
+
|
|
357
|
+
e_k3, g_k3, _, _ = self.CE.single_point(
|
|
358
|
+
self.final_directory,
|
|
359
|
+
self.element_list,
|
|
360
|
+
-1, # Temporary calculation, no step number
|
|
361
|
+
self.electric_charge_and_multiplicity,
|
|
362
|
+
self.xtb_method,
|
|
363
|
+
UnitValueLib().bohr2angstroms * coords_k3
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# Calculate bias potential at k3 point
|
|
367
|
+
_, _, B_g_k3, _ = CalcBiaspot.main(
|
|
368
|
+
e_k3, g_k3, coords_k3, self.element_list,
|
|
369
|
+
self.force_data, g_k3, -1, coords_k3
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
# Mass-weight k3 gradient
|
|
373
|
+
mw_B_g_k3 = self.mass_weight_gradient(B_g_k3, sqrt_mass_list)
|
|
374
|
+
k3 = self.get_k(mw_coords_k3, mw_B_g_k3)
|
|
375
|
+
|
|
376
|
+
# k4 calculation
|
|
377
|
+
mw_coords_k4 = mw_coords + k3
|
|
378
|
+
coords_k4 = self.unmass_weight_step(mw_coords_k4, sqrt_mass_list)
|
|
379
|
+
|
|
380
|
+
e_k4, g_k4, _, _ = self.CE.single_point(
|
|
381
|
+
self.final_directory,
|
|
382
|
+
self.element_list,
|
|
383
|
+
-1, # Temporary calculation, no step number
|
|
384
|
+
self.electric_charge_and_multiplicity,
|
|
385
|
+
self.xtb_method,
|
|
386
|
+
UnitValueLib().bohr2angstroms * coords_k4
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
# Calculate bias potential at k4 point
|
|
390
|
+
_, _, B_g_k4, _ = CalcBiaspot.main(
|
|
391
|
+
e_k4, g_k4, coords_k4, self.element_list,
|
|
392
|
+
self.force_data, g_k4, -1, coords_k4
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Mass-weight k4 gradient
|
|
396
|
+
mw_B_g_k4 = self.mass_weight_gradient(B_g_k4, sqrt_mass_list)
|
|
397
|
+
k4 = self.get_k(mw_coords_k4, mw_B_g_k4)
|
|
398
|
+
|
|
399
|
+
# Calculate step using RK4 formula
|
|
400
|
+
mw_step = (k1 + 2*k2 + 2*k3 + k4) / 6
|
|
401
|
+
step = self.unmass_weight_step(mw_step, sqrt_mass_list)
|
|
402
|
+
|
|
403
|
+
# Update geometry
|
|
404
|
+
new_geom = geom_num_list + step
|
|
405
|
+
|
|
406
|
+
# Remove center of mass motion
|
|
407
|
+
new_geom -= Calculationtools().calc_center_of_mass(new_geom, self.element_list)
|
|
408
|
+
|
|
409
|
+
return new_geom
|
|
410
|
+
|
|
411
|
+
def run(self):
|
|
412
|
+
"""Run the RK4 IRC calculation"""
|
|
413
|
+
print("Runge-Kutta 4th Order method")
|
|
414
|
+
geom_num_list = self.coords
|
|
415
|
+
CalcBiaspot = BiasPotentialCalculation(self.directory)
|
|
416
|
+
|
|
417
|
+
# Initialize oscillation counter
|
|
418
|
+
oscillation_counter = 0
|
|
419
|
+
|
|
420
|
+
for iter in range(1, self.max_step):
|
|
421
|
+
print("# STEP: ", iter)
|
|
422
|
+
exit_file_detect = os.path.exists(self.directory+"end.txt")
|
|
423
|
+
|
|
424
|
+
if exit_file_detect:
|
|
425
|
+
break
|
|
426
|
+
|
|
427
|
+
# Calculate energy, gradient and new geometry
|
|
428
|
+
e, g, geom_num_list, finish_frag = self.CE.single_point(
|
|
429
|
+
self.final_directory,
|
|
430
|
+
self.element_list,
|
|
431
|
+
iter,
|
|
432
|
+
self.electric_charge_and_multiplicity,
|
|
433
|
+
self.xtb_method,
|
|
434
|
+
UnitValueLib().bohr2angstroms * geom_num_list
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
# Calculate bias potential
|
|
438
|
+
_, B_e, B_g, BPA_hessian = CalcBiaspot.main(
|
|
439
|
+
e, g, geom_num_list, self.element_list,
|
|
440
|
+
self.force_data, g, iter-1, geom_num_list
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
if finish_frag:
|
|
444
|
+
break
|
|
445
|
+
|
|
446
|
+
# Recalculate Hessian if needed
|
|
447
|
+
if iter % self.FC_count == 0:
|
|
448
|
+
self.mw_hessian = self.CE.Model_hess
|
|
449
|
+
self.mw_hessian = Calculationtools().project_out_hess_tr_and_rot(
|
|
450
|
+
self.mw_hessian, self.element_list, geom_num_list
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
# Get mass arrays for consistent mass-weighting
|
|
454
|
+
elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
|
|
455
|
+
|
|
456
|
+
# Mass-weight the hessian
|
|
457
|
+
mw_BPA_hessian = self.mass_weight_hessian(BPA_hessian, three_sqrt_mass_list)
|
|
458
|
+
|
|
459
|
+
# Mass-weight the coordinates
|
|
460
|
+
mw_geom_num_list = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
|
|
461
|
+
|
|
462
|
+
# Mass-weight the gradients
|
|
463
|
+
mw_g = self.mass_weight_gradient(g, sqrt_mass_list)
|
|
464
|
+
mw_B_g = self.mass_weight_gradient(B_g, sqrt_mass_list)
|
|
465
|
+
|
|
466
|
+
# Save structure to XYZ file
|
|
467
|
+
self.save_xyz_structure(iter, geom_num_list)
|
|
468
|
+
|
|
469
|
+
# Save energy and gradient data to CSV
|
|
470
|
+
self.save_to_csv(iter, e, B_e, g, B_g)
|
|
471
|
+
|
|
472
|
+
# Store IRC data for calculation purposes (limit to keep only necessary data)
|
|
473
|
+
# Keep only last 3 points for calculations like path bending angles and hessian updates
|
|
474
|
+
if len(self.irc_energy_list) >= 3:
|
|
475
|
+
self.irc_energy_list.pop(0)
|
|
476
|
+
self.irc_bias_energy_list.pop(0)
|
|
477
|
+
self.irc_mw_coords.pop(0)
|
|
478
|
+
self.irc_mw_gradients.pop(0)
|
|
479
|
+
self.irc_mw_bias_gradients.pop(0)
|
|
480
|
+
|
|
481
|
+
self.irc_energy_list.append(e)
|
|
482
|
+
self.irc_bias_energy_list.append(B_e)
|
|
483
|
+
self.irc_mw_coords.append(mw_geom_num_list)
|
|
484
|
+
self.irc_mw_gradients.append(mw_g)
|
|
485
|
+
self.irc_mw_bias_gradients.append(mw_B_g)
|
|
486
|
+
|
|
487
|
+
# Check for energy oscillations
|
|
488
|
+
if self.check_energy_oscillation(self.irc_bias_energy_list):
|
|
489
|
+
oscillation_counter += 1
|
|
490
|
+
print(f"Energy oscillation detected ({oscillation_counter}/5)")
|
|
491
|
+
|
|
492
|
+
if oscillation_counter >= 5:
|
|
493
|
+
print("Terminating IRC: Energy oscillated for 5 consecutive steps")
|
|
494
|
+
break
|
|
495
|
+
else:
|
|
496
|
+
# Reset counter if no oscillation is detected
|
|
497
|
+
oscillation_counter = 0
|
|
498
|
+
|
|
499
|
+
if iter > 1:
|
|
500
|
+
# Take RK4 step
|
|
501
|
+
geom_num_list = self.step(
|
|
502
|
+
mw_B_g, geom_num_list, mw_BPA_hessian, sqrt_mass_list
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# Calculate path bending angle
|
|
506
|
+
if iter > 2:
|
|
507
|
+
bend_angle = Calculationtools().calc_multi_dim_vec_angle(
|
|
508
|
+
self.irc_mw_coords[0]-self.irc_mw_coords[1],
|
|
509
|
+
self.irc_mw_coords[2]-self.irc_mw_coords[1]
|
|
510
|
+
)
|
|
511
|
+
self.path_bending_angle_list.append(np.degrees(bend_angle))
|
|
512
|
+
print("Path bending angle: ", np.degrees(bend_angle))
|
|
513
|
+
|
|
514
|
+
# Check for convergence
|
|
515
|
+
if convergence_check(B_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
|
|
516
|
+
print("Convergence reached. (IRC)")
|
|
517
|
+
break
|
|
518
|
+
|
|
519
|
+
else:
|
|
520
|
+
# First step is simple scaling along the gradient direction
|
|
521
|
+
normalized_grad = mw_B_g / np.linalg.norm(mw_B_g.flatten())
|
|
522
|
+
step = -normalized_grad * self.step_size * 0.05
|
|
523
|
+
step = self.unmass_weight_step(step, sqrt_mass_list)
|
|
524
|
+
|
|
525
|
+
geom_num_list = geom_num_list + step
|
|
526
|
+
geom_num_list -= Calculationtools().calc_center_of_mass(geom_num_list, self.element_list)
|
|
527
|
+
|
|
528
|
+
# Print current geometry
|
|
529
|
+
print()
|
|
530
|
+
for i in range(len(geom_num_list)):
|
|
531
|
+
x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
|
|
532
|
+
y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
|
|
533
|
+
z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
|
|
534
|
+
print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
|
|
535
|
+
|
|
536
|
+
# Display information
|
|
537
|
+
print()
|
|
538
|
+
print("Energy : ", e)
|
|
539
|
+
print("Bias Energy : ", B_e)
|
|
540
|
+
print("RMS B. grad : ", np.sqrt((B_g**2).mean()))
|
|
541
|
+
|
|
542
|
+
if len(self.irc_mw_coords) > 1:
|
|
543
|
+
# Calculate curvature properties
|
|
544
|
+
unit_tangent_vector, curvature_vector, scalar_curvature, curvature_coupling = calc_irc_curvature_properties(
|
|
545
|
+
mw_B_g, self.irc_mw_gradients[-2], self.mw_hessian, self.step_size
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
print("Scalar curvature: ", scalar_curvature)
|
|
549
|
+
print("Curvature coupling: ", curvature_coupling.ravel())
|
|
550
|
+
|
|
551
|
+
# Save curvature properties to file
|
|
552
|
+
save_curvature_properties_to_file(
|
|
553
|
+
os.path.join(self.directory, "irc_curvature_properties.csv"),
|
|
554
|
+
scalar_curvature,
|
|
555
|
+
curvature_coupling
|
|
556
|
+
)
|
|
557
|
+
print()
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
# Save final data visualization
|
|
561
|
+
G = Graph(self.directory)
|
|
562
|
+
rms_gradient_list = []
|
|
563
|
+
with open(self.csv_filename, 'r') as csvfile:
|
|
564
|
+
reader = csv.reader(csvfile)
|
|
565
|
+
next(reader) # Skip header
|
|
566
|
+
for row in reader:
|
|
567
|
+
rms_gradient_list.append(float(row[3]))
|
|
568
|
+
|
|
569
|
+
G.single_plot(
|
|
570
|
+
np.array(range(len(self.path_bending_angle_list))),
|
|
571
|
+
np.array(self.path_bending_angle_list),
|
|
572
|
+
self.directory,
|
|
573
|
+
atom_num=0,
|
|
574
|
+
axis_name_1="# STEP",
|
|
575
|
+
axis_name_2="bending angle [degrees]",
|
|
576
|
+
name="IRC_bending"
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
return
|
|
File without changes
|