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/lqa.py
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
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 LQA:
|
|
16
|
+
"""Local quadratic approximation method for IRC calculations
|
|
17
|
+
|
|
18
|
+
References
|
|
19
|
+
----------
|
|
20
|
+
[1] J. Chem. Phys. 93, 5634–5642 (1990)
|
|
21
|
+
[2] J. Chem. Phys. 120, 9918–9924 (2004)
|
|
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 LQA 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.N_euler = 20000 # Number of Euler integration steps
|
|
59
|
+
self.ModelHessianUpdate = ModelHessianUpdate()
|
|
60
|
+
self.CE = calc_engine
|
|
61
|
+
self.FC_count = FC_count
|
|
62
|
+
|
|
63
|
+
# initial condition
|
|
64
|
+
self.coords = init_coord
|
|
65
|
+
self.init_hess = init_hess
|
|
66
|
+
self.mw_hessian = init_hess # Mass-weighted hessian
|
|
67
|
+
self.xtb_method = xtb_method
|
|
68
|
+
|
|
69
|
+
# convergence criteria
|
|
70
|
+
self.MAX_FORCE_THRESHOLD = 0.0004
|
|
71
|
+
self.RMS_FORCE_THRESHOLD = 0.0001
|
|
72
|
+
|
|
73
|
+
self.element_list = element_list
|
|
74
|
+
self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
|
|
75
|
+
self.directory = file_directory
|
|
76
|
+
self.final_directory = final_directory
|
|
77
|
+
self.force_data = force_data
|
|
78
|
+
|
|
79
|
+
# IRC data storage for current calculation (needed for immediate operations)
|
|
80
|
+
# These will no longer store the full trajectory but only recent points needed for calculations
|
|
81
|
+
self.irc_bias_energy_list = []
|
|
82
|
+
self.irc_energy_list = []
|
|
83
|
+
self.irc_mw_coords = []
|
|
84
|
+
self.irc_mw_gradients = []
|
|
85
|
+
self.irc_mw_bias_gradients = []
|
|
86
|
+
self.path_bending_angle_list = []
|
|
87
|
+
|
|
88
|
+
# Create data files
|
|
89
|
+
self.create_csv_file()
|
|
90
|
+
self.create_xyz_file()
|
|
91
|
+
|
|
92
|
+
def create_csv_file(self):
|
|
93
|
+
"""Create CSV file for energy and gradient data"""
|
|
94
|
+
self.csv_filename = os.path.join(self.directory, "irc_energies_gradients.csv")
|
|
95
|
+
with open(self.csv_filename, 'w', newline='') as csvfile:
|
|
96
|
+
writer = csv.writer(csvfile)
|
|
97
|
+
writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)', 'RMS Gradient', 'RMS Bias Gradient'])
|
|
98
|
+
|
|
99
|
+
def create_xyz_file(self):
|
|
100
|
+
"""Create XYZ file for structure data"""
|
|
101
|
+
self.xyz_filename = os.path.join(self.directory, "irc_structures.xyz")
|
|
102
|
+
# Create empty file (will be appended to later)
|
|
103
|
+
open(self.xyz_filename, 'w').close()
|
|
104
|
+
|
|
105
|
+
def save_to_csv(self, step, energy, bias_energy, gradient, bias_gradient):
|
|
106
|
+
"""Save energy and gradient data to CSV file
|
|
107
|
+
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
step : int
|
|
111
|
+
Current step number
|
|
112
|
+
energy : float
|
|
113
|
+
Energy in Hartree
|
|
114
|
+
bias_energy : float
|
|
115
|
+
Bias energy in Hartree
|
|
116
|
+
gradient : numpy.ndarray
|
|
117
|
+
Gradient array
|
|
118
|
+
bias_gradient : numpy.ndarray
|
|
119
|
+
Bias gradient array
|
|
120
|
+
"""
|
|
121
|
+
rms_grad = np.sqrt((gradient**2).mean())
|
|
122
|
+
rms_bias_grad = np.sqrt((bias_gradient**2).mean())
|
|
123
|
+
|
|
124
|
+
with open(self.csv_filename, 'a', newline='') as csvfile:
|
|
125
|
+
writer = csv.writer(csvfile)
|
|
126
|
+
writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad])
|
|
127
|
+
|
|
128
|
+
def save_xyz_structure(self, step, coords):
|
|
129
|
+
"""Save molecular structure to XYZ file
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
step : int
|
|
134
|
+
Current step number
|
|
135
|
+
coords : numpy.ndarray
|
|
136
|
+
Atomic coordinates in Bohr
|
|
137
|
+
"""
|
|
138
|
+
# Convert coordinates to Angstroms
|
|
139
|
+
coords_angstrom = coords * UnitValueLib().bohr2angstroms
|
|
140
|
+
|
|
141
|
+
with open(self.xyz_filename, 'a') as f:
|
|
142
|
+
# Number of atoms and comment line
|
|
143
|
+
f.write(f"{len(coords)}\n")
|
|
144
|
+
f.write(f"IRC Step {step}\n")
|
|
145
|
+
|
|
146
|
+
# Atomic coordinates
|
|
147
|
+
for i, coord in enumerate(coords_angstrom):
|
|
148
|
+
f.write(f"{self.element_list[i]:<3} {coord[0]:15.10f} {coord[1]:15.10f} {coord[2]:15.10f}\n")
|
|
149
|
+
|
|
150
|
+
def get_mass_array(self):
|
|
151
|
+
"""Create arrays of atomic masses for mass-weighting operations"""
|
|
152
|
+
elem_mass_list = np.array([atomic_mass(elem) for elem in self.element_list], dtype="float64")
|
|
153
|
+
sqrt_mass_list = np.sqrt(elem_mass_list)
|
|
154
|
+
|
|
155
|
+
# Create arrays for 3D operations (x,y,z for each atom)
|
|
156
|
+
three_elem_mass_list = np.repeat(elem_mass_list, 3)
|
|
157
|
+
three_sqrt_mass_list = np.repeat(sqrt_mass_list, 3)
|
|
158
|
+
|
|
159
|
+
return elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list
|
|
160
|
+
|
|
161
|
+
def mass_weight_hessian(self, hessian, three_sqrt_mass_list):
|
|
162
|
+
"""Apply mass-weighting to the hessian matrix
|
|
163
|
+
|
|
164
|
+
Parameters
|
|
165
|
+
----------
|
|
166
|
+
hessian : numpy.ndarray
|
|
167
|
+
Hessian matrix in non-mass-weighted coordinates
|
|
168
|
+
three_sqrt_mass_list : numpy.ndarray
|
|
169
|
+
Array of sqrt(mass) values repeated for x,y,z per atom
|
|
170
|
+
|
|
171
|
+
Returns
|
|
172
|
+
-------
|
|
173
|
+
numpy.ndarray
|
|
174
|
+
Mass-weighted hessian
|
|
175
|
+
"""
|
|
176
|
+
mass_mat = np.diag(1.0 / three_sqrt_mass_list)
|
|
177
|
+
return np.dot(mass_mat, np.dot(hessian, mass_mat))
|
|
178
|
+
|
|
179
|
+
def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
|
|
180
|
+
"""Convert coordinates to mass-weighted coordinates
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
coordinates : numpy.ndarray
|
|
185
|
+
Coordinates in non-mass-weighted form
|
|
186
|
+
sqrt_mass_list : numpy.ndarray
|
|
187
|
+
Array of sqrt(mass) values for each atom
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
numpy.ndarray
|
|
192
|
+
Mass-weighted coordinates
|
|
193
|
+
"""
|
|
194
|
+
mw_coords = copy.deepcopy(coordinates)
|
|
195
|
+
for i in range(len(coordinates)):
|
|
196
|
+
mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
|
|
197
|
+
return mw_coords
|
|
198
|
+
|
|
199
|
+
def mass_weight_gradient(self, gradient, sqrt_mass_list):
|
|
200
|
+
"""Convert gradient to mass-weighted form
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
gradient : numpy.ndarray
|
|
205
|
+
Gradient in non-mass-weighted form
|
|
206
|
+
sqrt_mass_list : numpy.ndarray
|
|
207
|
+
Array of sqrt(mass) values for each atom
|
|
208
|
+
|
|
209
|
+
Returns
|
|
210
|
+
-------
|
|
211
|
+
numpy.ndarray
|
|
212
|
+
Mass-weighted gradient
|
|
213
|
+
"""
|
|
214
|
+
mw_gradient = copy.deepcopy(gradient)
|
|
215
|
+
for i in range(len(gradient)):
|
|
216
|
+
mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
|
|
217
|
+
return mw_gradient
|
|
218
|
+
|
|
219
|
+
def unmass_weight_step(self, step, sqrt_mass_list):
|
|
220
|
+
"""Convert a step vector from mass-weighted to non-mass-weighted coordinates
|
|
221
|
+
|
|
222
|
+
Parameters
|
|
223
|
+
----------
|
|
224
|
+
step : numpy.ndarray
|
|
225
|
+
Step in mass-weighted form
|
|
226
|
+
sqrt_mass_list : numpy.ndarray
|
|
227
|
+
Array of sqrt(mass) values for each atom
|
|
228
|
+
|
|
229
|
+
Returns
|
|
230
|
+
-------
|
|
231
|
+
numpy.ndarray
|
|
232
|
+
Step in non-mass-weighted coordinates
|
|
233
|
+
"""
|
|
234
|
+
unmw_step = copy.deepcopy(step)
|
|
235
|
+
for i in range(len(step)):
|
|
236
|
+
unmw_step[i] = step[i] / sqrt_mass_list[i]
|
|
237
|
+
return unmw_step
|
|
238
|
+
|
|
239
|
+
def check_energy_oscillation(self, energy_list):
|
|
240
|
+
"""Check if energy is oscillating (going up and down)
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
energy_list : list
|
|
245
|
+
List of energy values
|
|
246
|
+
|
|
247
|
+
Returns
|
|
248
|
+
-------
|
|
249
|
+
bool
|
|
250
|
+
True if energy is oscillating, False otherwise
|
|
251
|
+
"""
|
|
252
|
+
if len(energy_list) < 3:
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
# Check if the energy changes direction (from increasing to decreasing or vice versa)
|
|
256
|
+
last_diff = energy_list[-1] - energy_list[-2]
|
|
257
|
+
prev_diff = energy_list[-2] - energy_list[-3]
|
|
258
|
+
|
|
259
|
+
# Return True if the energy direction has changed
|
|
260
|
+
return (last_diff * prev_diff) < 0
|
|
261
|
+
|
|
262
|
+
def step(self, mw_gradient, geom_num_list, mw_BPA_hessian, sqrt_mass_list):
|
|
263
|
+
"""Calculate a single LQA IRC step
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
mw_gradient : numpy.ndarray
|
|
268
|
+
Mass-weighted gradient
|
|
269
|
+
geom_num_list : numpy.ndarray
|
|
270
|
+
Current geometry coordinates
|
|
271
|
+
mw_BPA_hessian : numpy.ndarray
|
|
272
|
+
Mass-weighted bias potential hessian
|
|
273
|
+
sqrt_mass_list : numpy.ndarray
|
|
274
|
+
Array of sqrt(mass) values for each atom
|
|
275
|
+
|
|
276
|
+
Returns
|
|
277
|
+
-------
|
|
278
|
+
numpy.ndarray
|
|
279
|
+
New geometry coordinates
|
|
280
|
+
"""
|
|
281
|
+
# Update Hessian if we have previous points
|
|
282
|
+
if len(self.irc_mw_gradients) > 1 and len(self.irc_mw_coords) > 1:
|
|
283
|
+
delta_g = (self.irc_mw_gradients[-1] - self.irc_mw_gradients[-2]).reshape(-1, 1)
|
|
284
|
+
delta_x = (self.irc_mw_coords[-1] - self.irc_mw_coords[-2]).reshape(-1, 1)
|
|
285
|
+
|
|
286
|
+
# Only update if the step and gradient difference are meaningful
|
|
287
|
+
if np.dot(delta_x.T, delta_g)[0, 0] > 1e-10:
|
|
288
|
+
delta_hess = self.ModelHessianUpdate.BFGS_hessian_update(self.mw_hessian, delta_x, delta_g)
|
|
289
|
+
self.mw_hessian += delta_hess
|
|
290
|
+
|
|
291
|
+
# Add bias potential hessian and diagonalize
|
|
292
|
+
combined_hessian = self.mw_hessian + mw_BPA_hessian
|
|
293
|
+
eigenvalues, eigenvectors = np.linalg.eigh(combined_hessian)
|
|
294
|
+
|
|
295
|
+
# Drop small eigenvalues and corresponding eigenvectors
|
|
296
|
+
small_eigvals = np.abs(eigenvalues) < 1e-8
|
|
297
|
+
eigenvalues = eigenvalues[~small_eigvals]
|
|
298
|
+
eigenvectors = eigenvectors[:,~small_eigvals]
|
|
299
|
+
|
|
300
|
+
# Reshape gradient for matrix operations
|
|
301
|
+
flattened_gradient = mw_gradient.flatten()
|
|
302
|
+
|
|
303
|
+
# --- START MODIFICATION (Fix for numerical stability) ---
|
|
304
|
+
|
|
305
|
+
# Time step for numerical integration
|
|
306
|
+
# Original: dt = 1 / self.N_euler * self.step_size / np.linalg.norm(flattened_gradient)
|
|
307
|
+
# This can diverge if np.linalg.norm(flattened_gradient) -> 0
|
|
308
|
+
|
|
309
|
+
epsilon = 1e-6 # Prevent divergence when gradient norm is near zero
|
|
310
|
+
norm_g = np.linalg.norm(flattened_gradient)
|
|
311
|
+
dt = 1 / self.N_euler * self.step_size / max(norm_g, epsilon)
|
|
312
|
+
|
|
313
|
+
# --- END MODIFICATION ---
|
|
314
|
+
|
|
315
|
+
# Transform gradient to eigensystem of the hessian
|
|
316
|
+
mw_gradient_proj = np.dot(eigenvectors.T, flattened_gradient)
|
|
317
|
+
|
|
318
|
+
# Integration of the step size
|
|
319
|
+
t = dt
|
|
320
|
+
current_length = 0
|
|
321
|
+
for j in range(self.N_euler):
|
|
322
|
+
dsdt = np.sqrt(np.sum(mw_gradient_proj**2 * np.exp(-2*eigenvalues*t)))
|
|
323
|
+
current_length += dsdt * dt
|
|
324
|
+
if current_length > self.step_size:
|
|
325
|
+
break
|
|
326
|
+
t += dt
|
|
327
|
+
|
|
328
|
+
# --- START MODIFICATION (Fix for numerical stability) ---
|
|
329
|
+
|
|
330
|
+
# Calculate alphas and the IRC step
|
|
331
|
+
# Original: alphas = (np.exp(-eigenvalues*t) - 1) / eigenvalues
|
|
332
|
+
# This suffers from catastrophic cancellation (桁落ち) if (eigenvalues*t) is near zero.
|
|
333
|
+
|
|
334
|
+
x = -eigenvalues * t
|
|
335
|
+
|
|
336
|
+
# Use np.expm1(x) for numerical stability.
|
|
337
|
+
# np.expm1(x) calculates exp(x) - 1 accurately.
|
|
338
|
+
# We need (exp(x) - 1) / eigenvalues.
|
|
339
|
+
# Since x = -eigenvalues * t, then eigenvalues = -x / t
|
|
340
|
+
# (exp(x) - 1) / (-x / t) = -t * (exp(x) - 1) / x
|
|
341
|
+
|
|
342
|
+
# We use np.where to handle the limit x -> 0, where (exp(x)-1)/x -> 1, so alpha -> -t
|
|
343
|
+
small_x_mask = np.abs(x) < 1e-8
|
|
344
|
+
|
|
345
|
+
alphas = np.where(
|
|
346
|
+
small_x_mask,
|
|
347
|
+
-t, # Limit of (exp(x)-1)/eigenvalues as x->0 is -t
|
|
348
|
+
np.expm1(x) / eigenvalues # Use numerically stable function
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# --- END MODIFICATION ---
|
|
352
|
+
|
|
353
|
+
A = np.dot(eigenvectors, np.dot(np.diag(alphas), eigenvectors.T))
|
|
354
|
+
step = np.dot(A, flattened_gradient)
|
|
355
|
+
|
|
356
|
+
# Reshape and un-mass-weight the step
|
|
357
|
+
step = step.reshape(len(geom_num_list), 3)
|
|
358
|
+
step = self.unmass_weight_step(step, sqrt_mass_list)
|
|
359
|
+
|
|
360
|
+
# Update geometry
|
|
361
|
+
new_geom = geom_num_list + step
|
|
362
|
+
|
|
363
|
+
# Remove center of mass motion
|
|
364
|
+
new_geom -= Calculationtools().calc_center_of_mass(new_geom, self.element_list)
|
|
365
|
+
|
|
366
|
+
return new_geom
|
|
367
|
+
|
|
368
|
+
def run(self):
|
|
369
|
+
"""Run the LQA IRC calculation"""
|
|
370
|
+
print("Local Quadratic Approximation method")
|
|
371
|
+
geom_num_list = self.coords
|
|
372
|
+
CalcBiaspot = BiasPotentialCalculation(self.directory)
|
|
373
|
+
|
|
374
|
+
# Initialize oscillation counter
|
|
375
|
+
oscillation_counter = 0
|
|
376
|
+
|
|
377
|
+
for iter in range(1, self.max_step):
|
|
378
|
+
print("# STEP: ", iter)
|
|
379
|
+
exit_file_detect = os.path.exists(self.directory+"end.txt")
|
|
380
|
+
|
|
381
|
+
if exit_file_detect:
|
|
382
|
+
break
|
|
383
|
+
|
|
384
|
+
# Calculate energy, gradient and new geometry
|
|
385
|
+
e, g, geom_num_list, finish_frag = self.CE.single_point(
|
|
386
|
+
self.final_directory,
|
|
387
|
+
self.element_list,
|
|
388
|
+
iter,
|
|
389
|
+
self.electric_charge_and_multiplicity,
|
|
390
|
+
self.xtb_method,
|
|
391
|
+
UnitValueLib().bohr2angstroms * geom_num_list
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
# Calculate bias potential
|
|
395
|
+
_, B_e, B_g, BPA_hessian = CalcBiaspot.main(
|
|
396
|
+
e, g, geom_num_list, self.element_list,
|
|
397
|
+
self.force_data, g, iter-1, geom_num_list
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if finish_frag:
|
|
401
|
+
break
|
|
402
|
+
|
|
403
|
+
# Recalculate Hessian if needed
|
|
404
|
+
if iter % self.FC_count == 0 and iter > 0:
|
|
405
|
+
self.mw_hessian = self.CE.Model_hess
|
|
406
|
+
self.mw_hessian = Calculationtools().project_out_hess_tr_and_rot(
|
|
407
|
+
self.mw_hessian, self.element_list, geom_num_list
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
# Get mass arrays for consistent mass-weighting
|
|
411
|
+
elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
|
|
412
|
+
|
|
413
|
+
# Mass-weight the hessian
|
|
414
|
+
mw_BPA_hessian = self.mass_weight_hessian(BPA_hessian, three_sqrt_mass_list)
|
|
415
|
+
|
|
416
|
+
# Mass-weight the coordinates
|
|
417
|
+
mw_geom_num_list = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
|
|
418
|
+
|
|
419
|
+
# Mass-weight the gradients
|
|
420
|
+
mw_g = self.mass_weight_gradient(g, sqrt_mass_list)
|
|
421
|
+
mw_B_g = self.mass_weight_gradient(B_g, sqrt_mass_list)
|
|
422
|
+
|
|
423
|
+
# Save structure to XYZ file
|
|
424
|
+
self.save_xyz_structure(iter, geom_num_list)
|
|
425
|
+
|
|
426
|
+
# Save energy and gradient data to CSV
|
|
427
|
+
self.save_to_csv(iter, e, B_e, g, B_g)
|
|
428
|
+
|
|
429
|
+
# Store IRC data for calculation purposes (limit to keep only necessary data)
|
|
430
|
+
# Keep only last 3 points for calculations like path bending angles and hessian updates
|
|
431
|
+
if len(self.irc_energy_list) >= 3:
|
|
432
|
+
self.irc_energy_list.pop(0)
|
|
433
|
+
self.irc_bias_energy_list.pop(0)
|
|
434
|
+
self.irc_mw_coords.pop(0)
|
|
435
|
+
self.irc_mw_gradients.pop(0)
|
|
436
|
+
self.irc_mw_bias_gradients.pop(0)
|
|
437
|
+
|
|
438
|
+
self.irc_energy_list.append(e)
|
|
439
|
+
self.irc_bias_energy_list.append(B_e)
|
|
440
|
+
self.irc_mw_coords.append(mw_geom_num_list)
|
|
441
|
+
self.irc_mw_gradients.append(mw_g)
|
|
442
|
+
self.irc_mw_bias_gradients.append(mw_B_g)
|
|
443
|
+
|
|
444
|
+
# Check for energy oscillations
|
|
445
|
+
if self.check_energy_oscillation(self.irc_bias_energy_list):
|
|
446
|
+
oscillation_counter += 1
|
|
447
|
+
print(f"Energy oscillation detected ({oscillation_counter}/5)")
|
|
448
|
+
|
|
449
|
+
if oscillation_counter >= 5:
|
|
450
|
+
print("Terminating IRC: Energy oscillated for 5 consecutive steps")
|
|
451
|
+
break
|
|
452
|
+
else:
|
|
453
|
+
# Reset counter if no oscillation is detected
|
|
454
|
+
oscillation_counter = 0
|
|
455
|
+
|
|
456
|
+
if iter > 1:
|
|
457
|
+
# Take LQA step
|
|
458
|
+
geom_num_list = self.step(
|
|
459
|
+
mw_B_g, geom_num_list, mw_BPA_hessian, sqrt_mass_list
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# Calculate path bending angle
|
|
463
|
+
if iter > 2:
|
|
464
|
+
bend_angle = Calculationtools().calc_multi_dim_vec_angle(
|
|
465
|
+
self.irc_mw_coords[0]-self.irc_mw_coords[1],
|
|
466
|
+
self.irc_mw_coords[2]-self.irc_mw_coords[1]
|
|
467
|
+
)
|
|
468
|
+
self.path_bending_angle_list.append(np.degrees(bend_angle))
|
|
469
|
+
print("Path bending angle: ", np.degrees(bend_angle))
|
|
470
|
+
|
|
471
|
+
# Check for convergence
|
|
472
|
+
if convergence_check(B_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
|
|
473
|
+
print("Convergence reached. (IRC)")
|
|
474
|
+
break
|
|
475
|
+
|
|
476
|
+
else:
|
|
477
|
+
# First step is simple scaling along the gradient direction
|
|
478
|
+
normalized_grad = mw_B_g / np.linalg.norm(mw_B_g.flatten())
|
|
479
|
+
step = -normalized_grad * self.step_size * 0.05
|
|
480
|
+
step = self.unmass_weight_step(step, sqrt_mass_list)
|
|
481
|
+
|
|
482
|
+
geom_num_list = geom_num_list + step
|
|
483
|
+
geom_num_list -= Calculationtools().calc_center_of_mass(geom_num_list, self.element_list)
|
|
484
|
+
|
|
485
|
+
# Print current geometry
|
|
486
|
+
print()
|
|
487
|
+
for i in range(len(geom_num_list)):
|
|
488
|
+
x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
|
|
489
|
+
y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
|
|
490
|
+
z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
|
|
491
|
+
print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
|
|
492
|
+
|
|
493
|
+
# Display information
|
|
494
|
+
print()
|
|
495
|
+
print("Energy : ", e)
|
|
496
|
+
print("Bias Energy : ", B_e)
|
|
497
|
+
print("RMS B. grad : ", np.sqrt((B_g**2).mean()))
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
if len(self.irc_mw_coords) > 1:
|
|
501
|
+
# Calculate curvature properties
|
|
502
|
+
unit_tangent_vector, curvature_vector, scalar_curvature, curvature_coupling = calc_irc_curvature_properties(
|
|
503
|
+
mw_B_g, self.irc_mw_gradients[-2], self.mw_hessian, self.step_size
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
print("Scalar curvature: ", scalar_curvature)
|
|
507
|
+
# Print curvature_coupling as 6 columns, 8 decimal places
|
|
508
|
+
flat_cc = curvature_coupling.ravel()
|
|
509
|
+
print("Curvature coupling:")
|
|
510
|
+
for i in range(0, len(flat_cc), 6):
|
|
511
|
+
print(" ".join(f"{x: .8f}" for x in flat_cc[i:i+6]))
|
|
512
|
+
|
|
513
|
+
# Save curvature properties to file
|
|
514
|
+
save_curvature_properties_to_file(
|
|
515
|
+
os.path.join(self.directory, "irc_curvature_properties.csv"),
|
|
516
|
+
scalar_curvature,
|
|
517
|
+
curvature_coupling
|
|
518
|
+
)
|
|
519
|
+
print()
|
|
520
|
+
|
|
521
|
+
# Save final data visualization
|
|
522
|
+
G = Graph(self.directory)
|
|
523
|
+
rms_gradient_list = []
|
|
524
|
+
with open(self.csv_filename, 'r') as csvfile:
|
|
525
|
+
reader = csv.reader(csvfile)
|
|
526
|
+
next(reader) # Skip header
|
|
527
|
+
for row in reader:
|
|
528
|
+
rms_gradient_list.append(float(row[3]))
|
|
529
|
+
|
|
530
|
+
G.single_plot(
|
|
531
|
+
np.array(range(len(self.path_bending_angle_list))),
|
|
532
|
+
np.array(self.path_bending_angle_list),
|
|
533
|
+
self.directory,
|
|
534
|
+
atom_num=0,
|
|
535
|
+
axis_name_1="# STEP",
|
|
536
|
+
axis_name_2="bending angle [degrees]",
|
|
537
|
+
name="IRC_bending"
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
return
|