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/euler.py
ADDED
|
@@ -0,0 +1,439 @@
|
|
|
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 Euler:
|
|
16
|
+
"""Euler method for IRC calculations
|
|
17
|
+
|
|
18
|
+
A simple implementation of the IRC using Euler integration.
|
|
19
|
+
This method takes steps in the direction opposite to the gradient.
|
|
20
|
+
This method is not recommended for production use due to its simplicity
|
|
21
|
+
and potential instability, but serves as a basic example of IRC calculation.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, element_list, electric_charge_and_multiplicity, FC_count, file_directory,
|
|
26
|
+
final_directory, force_data, max_step=1000, step_size=0.01, init_coord=None,
|
|
27
|
+
init_hess=None, calc_engine=None, xtb_method=None, **kwargs):
|
|
28
|
+
"""Initialize Euler IRC calculator
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
element_list : list
|
|
33
|
+
List of atomic elements
|
|
34
|
+
electric_charge_and_multiplicity : tuple
|
|
35
|
+
Charge and multiplicity for the system
|
|
36
|
+
FC_count : int
|
|
37
|
+
Frequency of full hessian recalculation
|
|
38
|
+
file_directory : str
|
|
39
|
+
Working directory
|
|
40
|
+
final_directory : str
|
|
41
|
+
Directory for final output
|
|
42
|
+
force_data : dict
|
|
43
|
+
Force field data for bias potential
|
|
44
|
+
max_step : int, optional
|
|
45
|
+
Maximum number of steps
|
|
46
|
+
step_size : float, optional
|
|
47
|
+
Step size for the IRC
|
|
48
|
+
init_coord : numpy.ndarray, optional
|
|
49
|
+
Initial coordinates
|
|
50
|
+
init_hess : numpy.ndarray, optional
|
|
51
|
+
Initial hessian
|
|
52
|
+
calc_engine : object, optional
|
|
53
|
+
Calculator engine
|
|
54
|
+
xtb_method : str, optional
|
|
55
|
+
XTB method specification
|
|
56
|
+
"""
|
|
57
|
+
self.max_step = max_step
|
|
58
|
+
self.step_size = step_size
|
|
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.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
|
|
79
|
+
self.irc_bias_energy_list = []
|
|
80
|
+
self.irc_energy_list = []
|
|
81
|
+
self.irc_mw_coords = []
|
|
82
|
+
self.irc_mw_gradients = []
|
|
83
|
+
self.irc_mw_bias_gradients = []
|
|
84
|
+
self.path_length = 0.0
|
|
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', 'Path Length'])
|
|
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, path_length):
|
|
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
|
+
path_length : float
|
|
120
|
+
Current IRC path length
|
|
121
|
+
"""
|
|
122
|
+
rms_grad = np.sqrt((gradient**2).mean())
|
|
123
|
+
rms_bias_grad = np.sqrt((bias_gradient**2).mean())
|
|
124
|
+
|
|
125
|
+
with open(self.csv_filename, 'a', newline='') as csvfile:
|
|
126
|
+
writer = csv.writer(csvfile)
|
|
127
|
+
writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad, path_length])
|
|
128
|
+
|
|
129
|
+
def save_xyz_structure(self, step, coords):
|
|
130
|
+
"""Save molecular structure to XYZ file
|
|
131
|
+
|
|
132
|
+
Parameters
|
|
133
|
+
----------
|
|
134
|
+
step : int
|
|
135
|
+
Current step number
|
|
136
|
+
coords : numpy.ndarray
|
|
137
|
+
Atomic coordinates in Bohr
|
|
138
|
+
"""
|
|
139
|
+
# Convert coordinates to Angstroms
|
|
140
|
+
coords_angstrom = coords * UnitValueLib().bohr2angstroms
|
|
141
|
+
|
|
142
|
+
with open(self.xyz_filename, 'a') as f:
|
|
143
|
+
# Number of atoms and comment line
|
|
144
|
+
f.write(f"{len(coords)}\n")
|
|
145
|
+
f.write(f"IRC Step {step}\n")
|
|
146
|
+
|
|
147
|
+
# Atomic coordinates
|
|
148
|
+
for i, coord in enumerate(coords_angstrom):
|
|
149
|
+
f.write(f"{self.element_list[i]:<3} {coord[0]:15.10f} {coord[1]:15.10f} {coord[2]:15.10f}\n")
|
|
150
|
+
|
|
151
|
+
def get_mass_array(self):
|
|
152
|
+
"""Create arrays of atomic masses for mass-weighting operations"""
|
|
153
|
+
elem_mass_list = np.array([atomic_mass(elem) for elem in self.element_list], dtype="float64")
|
|
154
|
+
sqrt_mass_list = np.sqrt(elem_mass_list)
|
|
155
|
+
|
|
156
|
+
# Create arrays for 3D operations (x,y,z for each atom)
|
|
157
|
+
three_elem_mass_list = np.repeat(elem_mass_list, 3)
|
|
158
|
+
three_sqrt_mass_list = np.repeat(sqrt_mass_list, 3)
|
|
159
|
+
|
|
160
|
+
return elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list
|
|
161
|
+
|
|
162
|
+
def mass_weight_hessian(self, hessian, three_sqrt_mass_list):
|
|
163
|
+
"""Apply mass-weighting to the hessian matrix
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
hessian : numpy.ndarray
|
|
168
|
+
Hessian matrix in non-mass-weighted coordinates
|
|
169
|
+
three_sqrt_mass_list : numpy.ndarray
|
|
170
|
+
Array of sqrt(mass) values repeated for x,y,z per atom
|
|
171
|
+
|
|
172
|
+
Returns
|
|
173
|
+
-------
|
|
174
|
+
numpy.ndarray
|
|
175
|
+
Mass-weighted hessian
|
|
176
|
+
"""
|
|
177
|
+
mass_mat = np.diag(1.0 / three_sqrt_mass_list)
|
|
178
|
+
return np.dot(mass_mat, np.dot(hessian, mass_mat))
|
|
179
|
+
|
|
180
|
+
def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
|
|
181
|
+
"""Convert coordinates to mass-weighted coordinates
|
|
182
|
+
|
|
183
|
+
Parameters
|
|
184
|
+
----------
|
|
185
|
+
coordinates : numpy.ndarray
|
|
186
|
+
Coordinates in non-mass-weighted form
|
|
187
|
+
sqrt_mass_list : numpy.ndarray
|
|
188
|
+
Array of sqrt(mass) values for each atom
|
|
189
|
+
|
|
190
|
+
Returns
|
|
191
|
+
-------
|
|
192
|
+
numpy.ndarray
|
|
193
|
+
Mass-weighted coordinates
|
|
194
|
+
"""
|
|
195
|
+
mw_coords = copy.deepcopy(coordinates)
|
|
196
|
+
for i in range(len(coordinates)):
|
|
197
|
+
mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
|
|
198
|
+
return mw_coords
|
|
199
|
+
|
|
200
|
+
def mass_weight_gradient(self, gradient, sqrt_mass_list):
|
|
201
|
+
"""Convert gradient to mass-weighted form
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
gradient : numpy.ndarray
|
|
206
|
+
Gradient in non-mass-weighted form
|
|
207
|
+
sqrt_mass_list : numpy.ndarray
|
|
208
|
+
Array of sqrt(mass) values for each atom
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
numpy.ndarray
|
|
213
|
+
Mass-weighted gradient
|
|
214
|
+
"""
|
|
215
|
+
mw_gradient = copy.deepcopy(gradient)
|
|
216
|
+
for i in range(len(gradient)):
|
|
217
|
+
mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
|
|
218
|
+
return mw_gradient
|
|
219
|
+
|
|
220
|
+
def unmass_weight_step(self, step, sqrt_mass_list):
|
|
221
|
+
"""Convert a step vector from mass-weighted to non-mass-weighted coordinates
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
step : numpy.ndarray
|
|
226
|
+
Step in mass-weighted form
|
|
227
|
+
sqrt_mass_list : numpy.ndarray
|
|
228
|
+
Array of sqrt(mass) values for each atom
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
numpy.ndarray
|
|
233
|
+
Step in non-mass-weighted coordinates
|
|
234
|
+
"""
|
|
235
|
+
unmw_step = copy.deepcopy(step)
|
|
236
|
+
for i in range(len(step)):
|
|
237
|
+
unmw_step[i] = step[i] / sqrt_mass_list[i]
|
|
238
|
+
return unmw_step
|
|
239
|
+
|
|
240
|
+
def step(self, mw_gradient):
|
|
241
|
+
"""Calculate a single Euler IRC step
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
mw_gradient : numpy.ndarray
|
|
246
|
+
Mass-weighted gradient
|
|
247
|
+
|
|
248
|
+
Returns
|
|
249
|
+
-------
|
|
250
|
+
numpy.ndarray
|
|
251
|
+
Step vector in mass-weighted coordinates
|
|
252
|
+
float
|
|
253
|
+
Step length
|
|
254
|
+
"""
|
|
255
|
+
# Flatten the gradient for vector operations
|
|
256
|
+
grad = mw_gradient.flatten()
|
|
257
|
+
grad_norm = np.linalg.norm(grad)
|
|
258
|
+
|
|
259
|
+
# Step downhill, against the gradient
|
|
260
|
+
step_direction = -grad / grad_norm
|
|
261
|
+
step = self.step_size * step_direction
|
|
262
|
+
|
|
263
|
+
return step.reshape(mw_gradient.shape), self.step_size
|
|
264
|
+
|
|
265
|
+
def check_energy_oscillation(self, energy_list):
|
|
266
|
+
"""Check if energy is oscillating (going up and down)
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
energy_list : list
|
|
271
|
+
List of energy values
|
|
272
|
+
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
bool
|
|
276
|
+
True if energy is oscillating, False otherwise
|
|
277
|
+
"""
|
|
278
|
+
if len(energy_list) < 3:
|
|
279
|
+
return False
|
|
280
|
+
|
|
281
|
+
# Check if the energy changes direction (from increasing to decreasing or vice versa)
|
|
282
|
+
last_diff = energy_list[-1] - energy_list[-2]
|
|
283
|
+
prev_diff = energy_list[-2] - energy_list[-3]
|
|
284
|
+
|
|
285
|
+
# Return True if the energy direction has changed
|
|
286
|
+
return (last_diff * prev_diff) < 0
|
|
287
|
+
|
|
288
|
+
def run(self):
|
|
289
|
+
"""Run the Euler IRC calculation"""
|
|
290
|
+
print("Euler method for IRC calculation")
|
|
291
|
+
geom_num_list = self.coords
|
|
292
|
+
CalcBiaspot = BiasPotentialCalculation(self.directory)
|
|
293
|
+
|
|
294
|
+
# Initialize oscillation counter
|
|
295
|
+
oscillation_counter = 0
|
|
296
|
+
|
|
297
|
+
for iter in range(1, self.max_step + 1):
|
|
298
|
+
print("# STEP: ", iter)
|
|
299
|
+
exit_file_detect = os.path.exists(self.directory + "end.txt")
|
|
300
|
+
|
|
301
|
+
if exit_file_detect:
|
|
302
|
+
break
|
|
303
|
+
|
|
304
|
+
# Calculate energy, gradient and new geometry
|
|
305
|
+
e, g, geom_num_list, finish_frag = self.CE.single_point(
|
|
306
|
+
self.final_directory,
|
|
307
|
+
self.element_list,
|
|
308
|
+
iter,
|
|
309
|
+
self.electric_charge_and_multiplicity,
|
|
310
|
+
self.xtb_method,
|
|
311
|
+
UnitValueLib().bohr2angstroms * geom_num_list
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
# Calculate bias potential
|
|
315
|
+
_, B_e, B_g, BPA_hessian = CalcBiaspot.main(
|
|
316
|
+
e, g, geom_num_list, self.element_list,
|
|
317
|
+
self.force_data, g, iter-1, geom_num_list
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
if finish_frag:
|
|
321
|
+
break
|
|
322
|
+
|
|
323
|
+
# Get mass arrays for consistent mass-weighting
|
|
324
|
+
elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
|
|
325
|
+
|
|
326
|
+
# Mass-weight the gradients
|
|
327
|
+
mw_g = self.mass_weight_gradient(g, sqrt_mass_list)
|
|
328
|
+
mw_B_g = self.mass_weight_gradient(B_g, sqrt_mass_list)
|
|
329
|
+
|
|
330
|
+
# Mass-weight the coordinates
|
|
331
|
+
mw_geom_num_list = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
|
|
332
|
+
|
|
333
|
+
# Save structure to XYZ file
|
|
334
|
+
self.save_xyz_structure(iter, geom_num_list)
|
|
335
|
+
|
|
336
|
+
# Save energy and gradient data to CSV
|
|
337
|
+
self.save_to_csv(iter, e, B_e, g, B_g, self.path_length)
|
|
338
|
+
|
|
339
|
+
# Store IRC data for calculation purposes (limit to keep only necessary data)
|
|
340
|
+
# Keep only last 3 points for calculations like path bending angles
|
|
341
|
+
if len(self.irc_energy_list) >= 3:
|
|
342
|
+
self.irc_energy_list.pop(0)
|
|
343
|
+
self.irc_bias_energy_list.pop(0)
|
|
344
|
+
self.irc_mw_coords.pop(0)
|
|
345
|
+
self.irc_mw_gradients.pop(0)
|
|
346
|
+
self.irc_mw_bias_gradients.pop(0)
|
|
347
|
+
|
|
348
|
+
self.irc_energy_list.append(e)
|
|
349
|
+
self.irc_bias_energy_list.append(B_e)
|
|
350
|
+
self.irc_mw_coords.append(mw_geom_num_list)
|
|
351
|
+
self.irc_mw_gradients.append(mw_g)
|
|
352
|
+
self.irc_mw_bias_gradients.append(mw_B_g)
|
|
353
|
+
|
|
354
|
+
# Check for energy oscillations
|
|
355
|
+
if self.check_energy_oscillation(self.irc_bias_energy_list):
|
|
356
|
+
oscillation_counter += 1
|
|
357
|
+
print(f"Energy oscillation detected ({oscillation_counter}/5)")
|
|
358
|
+
|
|
359
|
+
if oscillation_counter >= 5:
|
|
360
|
+
print("Terminating IRC: Energy oscillated for 5 consecutive steps")
|
|
361
|
+
break
|
|
362
|
+
else:
|
|
363
|
+
# Reset counter if no oscillation is detected
|
|
364
|
+
oscillation_counter = 0
|
|
365
|
+
|
|
366
|
+
# Calculate path bending angle
|
|
367
|
+
if len(self.irc_mw_coords) >= 3:
|
|
368
|
+
bend_angle = Calculationtools().calc_multi_dim_vec_angle(
|
|
369
|
+
self.irc_mw_coords[0]-self.irc_mw_coords[1],
|
|
370
|
+
self.irc_mw_coords[2]-self.irc_mw_coords[1]
|
|
371
|
+
)
|
|
372
|
+
self.path_bending_angle_list.append(np.degrees(bend_angle))
|
|
373
|
+
print("Path bending angle: ", np.degrees(bend_angle))
|
|
374
|
+
|
|
375
|
+
# Calculate Euler step
|
|
376
|
+
mw_step, step_length = self.step(mw_B_g)
|
|
377
|
+
|
|
378
|
+
# Convert to non-mass-weighted step
|
|
379
|
+
step = self.unmass_weight_step(mw_step, sqrt_mass_list)
|
|
380
|
+
|
|
381
|
+
# Update geometry
|
|
382
|
+
geom_num_list = geom_num_list + step
|
|
383
|
+
|
|
384
|
+
# Remove center of mass motion
|
|
385
|
+
geom_num_list -= Calculationtools().calc_center_of_mass(geom_num_list, self.element_list)
|
|
386
|
+
|
|
387
|
+
# Update path length
|
|
388
|
+
self.path_length += step_length
|
|
389
|
+
|
|
390
|
+
# Check for convergence
|
|
391
|
+
if convergence_check(B_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
|
|
392
|
+
print("Convergence reached. (IRC)")
|
|
393
|
+
break
|
|
394
|
+
|
|
395
|
+
# Print current geometry
|
|
396
|
+
print()
|
|
397
|
+
for i in range(len(geom_num_list)):
|
|
398
|
+
x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
|
|
399
|
+
y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
|
|
400
|
+
z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
|
|
401
|
+
print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
|
|
402
|
+
|
|
403
|
+
# Display information
|
|
404
|
+
print()
|
|
405
|
+
print("Energy : ", e)
|
|
406
|
+
print("Bias Energy : ", B_e)
|
|
407
|
+
print("RMS B. grad : ", np.sqrt((B_g**2).mean()))
|
|
408
|
+
print("Path length : ", self.path_length)
|
|
409
|
+
|
|
410
|
+
if len(self.irc_mw_coords) > 1:
|
|
411
|
+
# Calculate curvature properties
|
|
412
|
+
unit_tangent_vector, curvature_vector, scalar_curvature, curvature_coupling = calc_irc_curvature_properties(
|
|
413
|
+
mw_B_g, self.irc_mw_gradients[-2], self.mw_hessian, self.step_size
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
print("Scalar curvature: ", scalar_curvature)
|
|
417
|
+
print("Curvature coupling: ", curvature_coupling.ravel())
|
|
418
|
+
|
|
419
|
+
# Save curvature properties to file
|
|
420
|
+
save_curvature_properties_to_file(
|
|
421
|
+
os.path.join(self.directory, "irc_curvature_properties.csv"),
|
|
422
|
+
scalar_curvature,
|
|
423
|
+
curvature_coupling
|
|
424
|
+
)
|
|
425
|
+
print()
|
|
426
|
+
|
|
427
|
+
# Save final data visualization
|
|
428
|
+
G = Graph(self.directory)
|
|
429
|
+
G.single_plot(
|
|
430
|
+
np.array(range(len(self.path_bending_angle_list))),
|
|
431
|
+
np.array(self.path_bending_angle_list),
|
|
432
|
+
self.directory,
|
|
433
|
+
atom_num=0,
|
|
434
|
+
axis_name_1="# STEP",
|
|
435
|
+
axis_name_2="bending angle [degrees]",
|
|
436
|
+
name="IRC_bending"
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
return
|