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/dvv.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
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.Visualization.visualization import Graph
|
|
11
|
+
from multioptpy.IRC.converge_criteria import convergence_check
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DVV:
|
|
15
|
+
"""Damped Velocity Verlet method for IRC calculations
|
|
16
|
+
|
|
17
|
+
This method uses a damped classical trajectory algorithm with dynamic time step control.
|
|
18
|
+
|
|
19
|
+
References
|
|
20
|
+
----------
|
|
21
|
+
[1] J. Phys. Chem. A, 106, 11, 2657-2667 (2002)
|
|
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, v0=0.04, dt0=0.5, error_tol=0.003, **kwargs):
|
|
27
|
+
"""Initialize DVV 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
|
+
Target velocity magnitude (v0)
|
|
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
|
+
v0 : float, optional
|
|
56
|
+
Target velocity magnitude (in atomic units), default=0.04
|
|
57
|
+
dt0 : float, optional
|
|
58
|
+
Initial time step (in fs), default=0.5
|
|
59
|
+
error_tol : float, optional
|
|
60
|
+
Error tolerance for time step adjustments, default=0.003
|
|
61
|
+
"""
|
|
62
|
+
self.max_step = max_step
|
|
63
|
+
self.step_size = step_size # Not directly used in DVV, but kept for consistency
|
|
64
|
+
self.ModelHessianUpdate = ModelHessianUpdate()
|
|
65
|
+
self.CE = calc_engine
|
|
66
|
+
self.FC_count = FC_count
|
|
67
|
+
|
|
68
|
+
# DVV specific parameters
|
|
69
|
+
self.v0 = v0
|
|
70
|
+
self.dt0 = dt0
|
|
71
|
+
self.error_tol = error_tol
|
|
72
|
+
|
|
73
|
+
# Initial condition
|
|
74
|
+
self.coords = init_coord
|
|
75
|
+
self.init_hess = init_hess
|
|
76
|
+
self.mw_hessian = init_hess # Mass-weighted hessian
|
|
77
|
+
self.xtb_method = xtb_method
|
|
78
|
+
|
|
79
|
+
# Convergence criteria
|
|
80
|
+
self.MAX_FORCE_THRESHOLD = 0.0004
|
|
81
|
+
self.RMS_FORCE_THRESHOLD = 0.0001
|
|
82
|
+
|
|
83
|
+
self.element_list = element_list
|
|
84
|
+
self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
|
|
85
|
+
self.directory = file_directory
|
|
86
|
+
self.final_directory = final_directory
|
|
87
|
+
self.force_data = force_data
|
|
88
|
+
|
|
89
|
+
# DVV trajectory data
|
|
90
|
+
self.velocities = [] # Store velocities
|
|
91
|
+
self.accelerations = [] # Store accelerations
|
|
92
|
+
self.time_steps = [] # Store time steps
|
|
93
|
+
|
|
94
|
+
# IRC data storage
|
|
95
|
+
self.irc_bias_energy_list = []
|
|
96
|
+
self.irc_energy_list = []
|
|
97
|
+
self.irc_mw_coords = []
|
|
98
|
+
self.irc_mw_gradients = []
|
|
99
|
+
self.irc_mw_bias_gradients = []
|
|
100
|
+
self.path_bending_angle_list = []
|
|
101
|
+
|
|
102
|
+
# Constants for unit conversion
|
|
103
|
+
self.BOHR2M = 5.29177210903e-11 # Bohr to meters
|
|
104
|
+
self.AU2J = 4.359744650e-18 # Hartree to Joules
|
|
105
|
+
self.AMU2KG = 1.660539040e-27 # AMU to kg
|
|
106
|
+
|
|
107
|
+
# Create data files
|
|
108
|
+
self.create_csv_file()
|
|
109
|
+
self.create_xyz_file()
|
|
110
|
+
|
|
111
|
+
def create_csv_file(self):
|
|
112
|
+
"""Create CSV file for energy and gradient data"""
|
|
113
|
+
self.csv_filename = os.path.join(self.directory, "irc_energies_gradients.csv")
|
|
114
|
+
with open(self.csv_filename, 'w', newline='') as csvfile:
|
|
115
|
+
writer = csv.writer(csvfile)
|
|
116
|
+
writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)',
|
|
117
|
+
'RMS Gradient', 'RMS Bias Gradient', 'Time Step (fs)', 'Damping Factor'])
|
|
118
|
+
|
|
119
|
+
def create_xyz_file(self):
|
|
120
|
+
"""Create XYZ file for structure data"""
|
|
121
|
+
self.xyz_filename = os.path.join(self.directory, "irc_structures.xyz")
|
|
122
|
+
# Create empty file (will be appended to later)
|
|
123
|
+
open(self.xyz_filename, 'w').close()
|
|
124
|
+
|
|
125
|
+
def save_to_csv(self, step, energy, bias_energy, gradient, bias_gradient, time_step=None, damping=None):
|
|
126
|
+
"""Save energy and gradient data to CSV file"""
|
|
127
|
+
rms_grad = np.sqrt((gradient**2).mean())
|
|
128
|
+
rms_bias_grad = np.sqrt((bias_gradient**2).mean())
|
|
129
|
+
|
|
130
|
+
with open(self.csv_filename, 'a', newline='') as csvfile:
|
|
131
|
+
writer = csv.writer(csvfile)
|
|
132
|
+
writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad,
|
|
133
|
+
time_step if time_step is not None else '',
|
|
134
|
+
damping if damping is not None else ''])
|
|
135
|
+
|
|
136
|
+
def save_xyz_structure(self, step, coords):
|
|
137
|
+
"""Save molecular structure to XYZ file"""
|
|
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
|
+
mass_mat = np.diag(1.0 / three_sqrt_mass_list)
|
|
164
|
+
return np.dot(mass_mat, np.dot(hessian, mass_mat))
|
|
165
|
+
|
|
166
|
+
def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
|
|
167
|
+
"""Convert coordinates to mass-weighted coordinates"""
|
|
168
|
+
mw_coords = copy.deepcopy(coordinates)
|
|
169
|
+
for i in range(len(coordinates)):
|
|
170
|
+
mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
|
|
171
|
+
return mw_coords
|
|
172
|
+
|
|
173
|
+
def mass_weight_gradient(self, gradient, sqrt_mass_list):
|
|
174
|
+
"""Convert gradient to mass-weighted form"""
|
|
175
|
+
mw_gradient = copy.deepcopy(gradient)
|
|
176
|
+
for i in range(len(gradient)):
|
|
177
|
+
mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
|
|
178
|
+
return mw_gradient
|
|
179
|
+
|
|
180
|
+
def unmass_weight_step(self, step, sqrt_mass_list):
|
|
181
|
+
"""Convert a step vector from mass-weighted to non-mass-weighted coordinates"""
|
|
182
|
+
unmw_step = copy.deepcopy(step)
|
|
183
|
+
for i in range(len(step)):
|
|
184
|
+
unmw_step[i] = step[i] / sqrt_mass_list[i]
|
|
185
|
+
return unmw_step
|
|
186
|
+
|
|
187
|
+
def mw_grad_to_acc(self, mw_gradient):
|
|
188
|
+
"""Convert mass-weighted gradient to acceleration with proper units
|
|
189
|
+
|
|
190
|
+
Converts units of mass-weighted gradient [Hartree/(Bohr*sqrt(amu))]
|
|
191
|
+
to units of acceleration [sqrt(amu)*Bohr/fs²]
|
|
192
|
+
|
|
193
|
+
Parameters
|
|
194
|
+
----------
|
|
195
|
+
mw_gradient : numpy.ndarray
|
|
196
|
+
Mass-weighted gradient
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
numpy.ndarray
|
|
201
|
+
Acceleration with proper units
|
|
202
|
+
"""
|
|
203
|
+
# The 1e30 comes from converting second² to femto second²
|
|
204
|
+
return mw_gradient * self.AU2J / self.AMU2KG / self.BOHR2M**2 / 1e30
|
|
205
|
+
|
|
206
|
+
def damp_velocity(self, velocity):
|
|
207
|
+
"""Apply damping to velocity to maintain consistent step size
|
|
208
|
+
|
|
209
|
+
Parameters
|
|
210
|
+
----------
|
|
211
|
+
velocity : numpy.ndarray
|
|
212
|
+
Velocity vector
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
tuple
|
|
217
|
+
(damped_velocity, damping_factor)
|
|
218
|
+
"""
|
|
219
|
+
# Compute damping factor to maintain consistent magnitude
|
|
220
|
+
v_norm = np.linalg.norm(velocity)
|
|
221
|
+
if v_norm < 1e-10:
|
|
222
|
+
damping_factor = 1.0
|
|
223
|
+
else:
|
|
224
|
+
damping_factor = self.v0 / v_norm
|
|
225
|
+
|
|
226
|
+
print(f"Damping factor={damping_factor:.6f}")
|
|
227
|
+
damped_velocity = velocity * damping_factor
|
|
228
|
+
return damped_velocity, damping_factor
|
|
229
|
+
|
|
230
|
+
def estimate_error(self, new_mw_coords, sqrt_mass_list):
|
|
231
|
+
"""Estimate error for time step adjustment
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
new_mw_coords : numpy.ndarray
|
|
236
|
+
New mass-weighted coordinates
|
|
237
|
+
sqrt_mass_list : numpy.ndarray
|
|
238
|
+
Array of sqrt(mass) values for each atom
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
float
|
|
243
|
+
Estimated error for time step adjustment
|
|
244
|
+
"""
|
|
245
|
+
print("Error estimation")
|
|
246
|
+
|
|
247
|
+
if len(self.irc_mw_coords) < 2 or len(self.time_steps) < 2 or len(self.velocities) < 2:
|
|
248
|
+
# Not enough history for error estimation
|
|
249
|
+
return self.error_tol
|
|
250
|
+
|
|
251
|
+
# Get current and previous time steps
|
|
252
|
+
cur_time_step = self.time_steps[-1]
|
|
253
|
+
prev_time_step = self.time_steps[-2]
|
|
254
|
+
time_step_sum = prev_time_step + cur_time_step
|
|
255
|
+
print(f"\tSum of current and previous timestep: {time_step_sum:.6f} fs")
|
|
256
|
+
|
|
257
|
+
# Calculate reference coordinates for error estimation (x' in the paper)
|
|
258
|
+
ref_coords = (
|
|
259
|
+
self.irc_mw_coords[-2] # x_i-2 in the paper
|
|
260
|
+
+ np.dot(self.velocities[-2].reshape(-1), time_step_sum).reshape(new_mw_coords.shape) # v_i-2 * dt_sum
|
|
261
|
+
+ 0.5 * np.dot(self.accelerations[-2].reshape(-1), time_step_sum**2).reshape(new_mw_coords.shape) # 0.5 * a_i-2 * dt_sum^2
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
# Calculate difference and scale by mass
|
|
265
|
+
diff = new_mw_coords - ref_coords
|
|
266
|
+
for i in range(len(diff)):
|
|
267
|
+
diff[i] = diff[i] / sqrt_mass_list[i]
|
|
268
|
+
|
|
269
|
+
# Calculate error metrics
|
|
270
|
+
largest_component = np.max(np.abs(diff))
|
|
271
|
+
norm = np.linalg.norm(diff)
|
|
272
|
+
|
|
273
|
+
print(f"\tmax(|diff|)={largest_component:.6f}")
|
|
274
|
+
print(f"\tnorm(diff)={norm:.6f}")
|
|
275
|
+
|
|
276
|
+
# Use the larger of the two metrics as the estimated error
|
|
277
|
+
estimated_error = max(largest_component, norm)
|
|
278
|
+
print(f"\testimated error={estimated_error:.6f}")
|
|
279
|
+
|
|
280
|
+
return estimated_error
|
|
281
|
+
|
|
282
|
+
def check_energy_oscillation(self, energy_list):
|
|
283
|
+
"""Check if energy is oscillating (going up and down)"""
|
|
284
|
+
if len(energy_list) < 3:
|
|
285
|
+
return False
|
|
286
|
+
|
|
287
|
+
# Check if the energy changes direction (from increasing to decreasing or vice versa)
|
|
288
|
+
last_diff = energy_list[-1] - energy_list[-2]
|
|
289
|
+
prev_diff = energy_list[-2] - energy_list[-3]
|
|
290
|
+
|
|
291
|
+
# Return True if the energy direction has changed
|
|
292
|
+
return (last_diff * prev_diff) < 0
|
|
293
|
+
|
|
294
|
+
def step(self, mw_gradient, geom_num_list, mw_BPA_hessian, sqrt_mass_list):
|
|
295
|
+
"""Calculate a single DVV IRC step
|
|
296
|
+
|
|
297
|
+
Parameters
|
|
298
|
+
----------
|
|
299
|
+
mw_gradient : numpy.ndarray
|
|
300
|
+
Mass-weighted gradient
|
|
301
|
+
geom_num_list : numpy.ndarray
|
|
302
|
+
Current geometry coordinates
|
|
303
|
+
mw_BPA_hessian : numpy.ndarray
|
|
304
|
+
Mass-weighted bias potential hessian (not used in DVV)
|
|
305
|
+
sqrt_mass_list : numpy.ndarray
|
|
306
|
+
Array of sqrt(mass) values for each atom
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
numpy.ndarray
|
|
311
|
+
New geometry coordinates
|
|
312
|
+
"""
|
|
313
|
+
# First-time setup if needed
|
|
314
|
+
if not self.velocities:
|
|
315
|
+
# Initialize with current time step
|
|
316
|
+
self.time_steps.append(self.dt0)
|
|
317
|
+
|
|
318
|
+
# Convert gradient to acceleration (negative because gradient points uphill)
|
|
319
|
+
acceleration = -self.mw_grad_to_acc(mw_gradient)
|
|
320
|
+
|
|
321
|
+
# Initialize velocity with damped acceleration
|
|
322
|
+
initial_velocity, _ = self.damp_velocity(acceleration)
|
|
323
|
+
|
|
324
|
+
self.velocities.append(initial_velocity)
|
|
325
|
+
self.accelerations.append(acceleration)
|
|
326
|
+
|
|
327
|
+
# Convert mw_coords to mass weighted
|
|
328
|
+
mw_coords = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
|
|
329
|
+
|
|
330
|
+
# For first step, don't update coords yet
|
|
331
|
+
return geom_num_list
|
|
332
|
+
|
|
333
|
+
# Get previous values
|
|
334
|
+
prev_time_step = self.time_steps[-1]
|
|
335
|
+
prev_acceleration = self.accelerations[-1]
|
|
336
|
+
prev_velocity = self.velocities[-1]
|
|
337
|
+
|
|
338
|
+
# Convert mw_coords to mass weighted
|
|
339
|
+
mw_coords = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
|
|
340
|
+
|
|
341
|
+
# Calculate new acceleration from current gradient
|
|
342
|
+
acceleration = -self.mw_grad_to_acc(mw_gradient)
|
|
343
|
+
|
|
344
|
+
# Check for velocity-acceleration alignment (for logging)
|
|
345
|
+
acc_normed = acceleration / np.linalg.norm(acceleration)
|
|
346
|
+
prev_vel_normed = prev_velocity / np.linalg.norm(prev_velocity)
|
|
347
|
+
ovlp = np.sum(acc_normed * prev_vel_normed)
|
|
348
|
+
print(f"a @ v_i-1={ovlp:.8f}")
|
|
349
|
+
|
|
350
|
+
# Store acceleration
|
|
351
|
+
self.accelerations.append(acceleration)
|
|
352
|
+
|
|
353
|
+
# Update coordinates using Velocity Verlet algorithm (Eq. 2 in the paper)
|
|
354
|
+
new_mw_coords = (mw_coords
|
|
355
|
+
+ prev_velocity * prev_time_step
|
|
356
|
+
+ 0.5 * prev_acceleration * prev_time_step**2)
|
|
357
|
+
|
|
358
|
+
# Update velocity (Eq. 3 in the paper)
|
|
359
|
+
velocity = prev_velocity + 0.5 * (prev_acceleration + acceleration) * prev_time_step
|
|
360
|
+
|
|
361
|
+
# Damp velocity to maintain consistent step size
|
|
362
|
+
damped_velocity, damping_factor = self.damp_velocity(velocity)
|
|
363
|
+
self.velocities.append(damped_velocity)
|
|
364
|
+
|
|
365
|
+
# Estimate error for time step adjustment
|
|
366
|
+
estimated_error = self.estimate_error(new_mw_coords, sqrt_mass_list)
|
|
367
|
+
|
|
368
|
+
# Adjust time step based on error (Eq. 6 in the paper)
|
|
369
|
+
new_time_step = prev_time_step * (self.error_tol / estimated_error)**(1/3)
|
|
370
|
+
|
|
371
|
+
# Constrain time step between 0.0025 fs and 3.0 fs
|
|
372
|
+
new_time_step = min(new_time_step, 3.0)
|
|
373
|
+
new_time_step = max(new_time_step, 0.025)
|
|
374
|
+
|
|
375
|
+
print(f"\tCurrent time step={prev_time_step:.6f} fs")
|
|
376
|
+
print(f"\tNext time step={new_time_step:.6f} fs")
|
|
377
|
+
|
|
378
|
+
# Store the new time step
|
|
379
|
+
self.time_steps.append(new_time_step)
|
|
380
|
+
|
|
381
|
+
# Convert back to non-mass-weighted
|
|
382
|
+
new_geom = self.unmass_weight_step(new_mw_coords, sqrt_mass_list)
|
|
383
|
+
|
|
384
|
+
# Remove center of mass motion
|
|
385
|
+
new_geom -= Calculationtools().calc_center_of_mass(new_geom, self.element_list)
|
|
386
|
+
|
|
387
|
+
return new_geom
|
|
388
|
+
|
|
389
|
+
def run(self):
|
|
390
|
+
"""Run the DVV IRC calculation"""
|
|
391
|
+
print("Damped Velocity Verlet method")
|
|
392
|
+
geom_num_list = self.coords
|
|
393
|
+
CalcBiaspot = BiasPotentialCalculation(self.directory)
|
|
394
|
+
|
|
395
|
+
# Initialize oscillation counter
|
|
396
|
+
oscillation_counter = 0
|
|
397
|
+
|
|
398
|
+
for iter in range(1, self.max_step):
|
|
399
|
+
print("# STEP: ", iter)
|
|
400
|
+
exit_file_detect = os.path.exists(self.directory+"end.txt")
|
|
401
|
+
|
|
402
|
+
if exit_file_detect:
|
|
403
|
+
break
|
|
404
|
+
|
|
405
|
+
# Calculate energy, gradient and new geometry
|
|
406
|
+
e, g, geom_num_list, finish_frag = self.CE.single_point(
|
|
407
|
+
self.final_directory,
|
|
408
|
+
self.element_list,
|
|
409
|
+
iter,
|
|
410
|
+
self.electric_charge_and_multiplicity,
|
|
411
|
+
self.xtb_method,
|
|
412
|
+
UnitValueLib().bohr2angstroms * geom_num_list
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
# Calculate bias potential
|
|
416
|
+
_, B_e, B_g, BPA_hessian = CalcBiaspot.main(
|
|
417
|
+
e, g, geom_num_list, self.element_list,
|
|
418
|
+
self.force_data, g, iter-1, geom_num_list
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if finish_frag:
|
|
422
|
+
break
|
|
423
|
+
|
|
424
|
+
# Recalculate Hessian if needed
|
|
425
|
+
if iter % self.FC_count == 0:
|
|
426
|
+
self.mw_hessian = self.CE.Model_hess
|
|
427
|
+
self.mw_hessian = Calculationtools().project_out_hess_tr_and_rot(
|
|
428
|
+
self.mw_hessian, self.element_list, geom_num_list
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Get mass arrays for consistent mass-weighting
|
|
432
|
+
elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
|
|
433
|
+
|
|
434
|
+
# Mass-weight the hessian
|
|
435
|
+
mw_BPA_hessian = self.mass_weight_hessian(BPA_hessian, three_sqrt_mass_list)
|
|
436
|
+
|
|
437
|
+
# Mass-weight the coordinates
|
|
438
|
+
mw_geom_num_list = self.mass_weight_coordinates(geom_num_list, sqrt_mass_list)
|
|
439
|
+
|
|
440
|
+
# Mass-weight the gradients
|
|
441
|
+
mw_g = self.mass_weight_gradient(g, sqrt_mass_list)
|
|
442
|
+
mw_B_g = self.mass_weight_gradient(B_g, sqrt_mass_list)
|
|
443
|
+
|
|
444
|
+
# Save structure to XYZ file
|
|
445
|
+
self.save_xyz_structure(iter, geom_num_list)
|
|
446
|
+
|
|
447
|
+
# Save energy and gradient data to CSV
|
|
448
|
+
current_dt = self.time_steps[-1] if self.time_steps else self.dt0
|
|
449
|
+
current_damping = damping_factor if 'damping_factor' in locals() else None
|
|
450
|
+
self.save_to_csv(iter, e, B_e, g, B_g, current_dt, current_damping)
|
|
451
|
+
|
|
452
|
+
# Store IRC data for calculation purposes (limit to keep only necessary data)
|
|
453
|
+
# Keep only last 3 points for calculations like path bending angles and hessian updates
|
|
454
|
+
if len(self.irc_energy_list) >= 3:
|
|
455
|
+
self.irc_energy_list.pop(0)
|
|
456
|
+
self.irc_bias_energy_list.pop(0)
|
|
457
|
+
self.irc_mw_coords.pop(0)
|
|
458
|
+
self.irc_mw_gradients.pop(0)
|
|
459
|
+
self.irc_mw_bias_gradients.pop(0)
|
|
460
|
+
|
|
461
|
+
self.irc_energy_list.append(e)
|
|
462
|
+
self.irc_bias_energy_list.append(B_e)
|
|
463
|
+
self.irc_mw_coords.append(mw_geom_num_list)
|
|
464
|
+
self.irc_mw_gradients.append(mw_g)
|
|
465
|
+
self.irc_mw_bias_gradients.append(mw_B_g)
|
|
466
|
+
|
|
467
|
+
# Check for energy oscillations
|
|
468
|
+
if self.check_energy_oscillation(self.irc_bias_energy_list):
|
|
469
|
+
oscillation_counter += 1
|
|
470
|
+
print(f"Energy oscillation detected ({oscillation_counter}/5)")
|
|
471
|
+
|
|
472
|
+
if oscillation_counter >= 5:
|
|
473
|
+
print("Terminating IRC: Energy oscillated for 5 consecutive steps")
|
|
474
|
+
break
|
|
475
|
+
else:
|
|
476
|
+
# Reset counter if no oscillation is detected
|
|
477
|
+
oscillation_counter = 0
|
|
478
|
+
|
|
479
|
+
# Take DVV step
|
|
480
|
+
geom_num_list = self.step(mw_B_g, geom_num_list, mw_BPA_hessian, sqrt_mass_list)
|
|
481
|
+
|
|
482
|
+
# Calculate path bending angle if we have enough points
|
|
483
|
+
if iter > 2 and len(self.irc_mw_coords) >= 3:
|
|
484
|
+
bend_angle = Calculationtools().calc_multi_dim_vec_angle(
|
|
485
|
+
self.irc_mw_coords[0]-self.irc_mw_coords[1],
|
|
486
|
+
self.irc_mw_coords[2]-self.irc_mw_coords[1]
|
|
487
|
+
)
|
|
488
|
+
self.path_bending_angle_list.append(np.degrees(bend_angle))
|
|
489
|
+
print("Path bending angle: ", np.degrees(bend_angle))
|
|
490
|
+
|
|
491
|
+
# Check for convergence
|
|
492
|
+
if convergence_check(B_g, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
|
|
493
|
+
print("Convergence reached. (IRC)")
|
|
494
|
+
break
|
|
495
|
+
|
|
496
|
+
# Print current geometry
|
|
497
|
+
print()
|
|
498
|
+
for i in range(len(geom_num_list)):
|
|
499
|
+
x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
|
|
500
|
+
y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
|
|
501
|
+
z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
|
|
502
|
+
print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
|
|
503
|
+
|
|
504
|
+
# Display information
|
|
505
|
+
print()
|
|
506
|
+
print("Energy : ", e)
|
|
507
|
+
print("Bias Energy : ", B_e)
|
|
508
|
+
print("RMS B. grad : ", np.sqrt((B_g**2).mean()))
|
|
509
|
+
if self.time_steps:
|
|
510
|
+
print(f"Time step : {self.time_steps[-1]:.6f} fs")
|
|
511
|
+
print()
|
|
512
|
+
|
|
513
|
+
# Save final data visualization
|
|
514
|
+
G = Graph(self.directory)
|
|
515
|
+
rms_gradient_list = []
|
|
516
|
+
with open(self.csv_filename, 'r') as csvfile:
|
|
517
|
+
reader = csv.reader(csvfile)
|
|
518
|
+
next(reader) # Skip header
|
|
519
|
+
for row in reader:
|
|
520
|
+
rms_gradient_list.append(float(row[3]))
|
|
521
|
+
|
|
522
|
+
G.single_plot(
|
|
523
|
+
np.array(range(len(self.path_bending_angle_list))),
|
|
524
|
+
np.array(self.path_bending_angle_list),
|
|
525
|
+
self.directory,
|
|
526
|
+
atom_num=0,
|
|
527
|
+
axis_name_1="# STEP",
|
|
528
|
+
axis_name_2="bending angle [degrees]",
|
|
529
|
+
name="IRC_bending"
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
# Also plot the time step evolution
|
|
533
|
+
G.single_plot(
|
|
534
|
+
np.array(range(len(self.time_steps))),
|
|
535
|
+
np.array(self.time_steps),
|
|
536
|
+
self.directory,
|
|
537
|
+
atom_num=0,
|
|
538
|
+
axis_name_1="# STEP",
|
|
539
|
+
axis_name_2="Time step (fs)",
|
|
540
|
+
name="DVV_timesteps"
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
return
|
|
544
|
+
|