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/hpc.py
ADDED
|
@@ -0,0 +1,564 @@
|
|
|
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
|
+
# --- Helper Class for HPC (DWI) ---
|
|
15
|
+
|
|
16
|
+
class DWISurface:
|
|
17
|
+
"""
|
|
18
|
+
Distance Weighted Interpolant (DWI) surface using data from two points
|
|
19
|
+
(position, energy, gradient, Hessian) [cite_start][cite: 79-80].
|
|
20
|
+
This surface can calculate the energy and gradient at any arbitrary point x.
|
|
21
|
+
|
|
22
|
+
Ref: J. Chem. Phys. 120, 9918 (2004), Sec II. [cite_start]D [cite: 239-251]
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, x1, e1, g1, h1, x2, e2, g2, h2):
|
|
25
|
+
self.natoms = x1.shape[0]
|
|
26
|
+
self.dim = self.natoms * 3
|
|
27
|
+
|
|
28
|
+
# Store data as flat (3N,) arrays
|
|
29
|
+
self.x = [x1.flatten(), x2.flatten()]
|
|
30
|
+
self.e = [e1, e2]
|
|
31
|
+
self.g = [g1.flatten(), g2.flatten()]
|
|
32
|
+
self.h = [h1, h2] # (3N, 3N)
|
|
33
|
+
|
|
34
|
+
def get_taylor(self, i, x_flat):
|
|
35
|
+
"""Calculates the Taylor expansion T_i(x) using data from point i"""
|
|
36
|
+
dx = x_flat - self.x[i]
|
|
37
|
+
e_taylor = self.e[i] \
|
|
38
|
+
+ np.dot(self.g[i].T, dx) \
|
|
39
|
+
+ 0.5 * np.dot(dx.T, np.dot(self.h[i], dx))
|
|
40
|
+
return e_taylor
|
|
41
|
+
|
|
42
|
+
def get_taylor_grad(self, i, x_flat):
|
|
43
|
+
"""Calculates the gradient ∇T_i(x) of the Taylor expansion T_i(x)"""
|
|
44
|
+
dx = x_flat - self.x[i]
|
|
45
|
+
# ∇T_i = g_i + H_i * (x - x_i)
|
|
46
|
+
g_taylor = self.g[i] + np.dot(self.h[i], dx)
|
|
47
|
+
return g_taylor
|
|
48
|
+
|
|
49
|
+
def get_weights(self, x_flat):
|
|
50
|
+
"""Calculate weights w1, w2 at point x [cite: 247-248]"""
|
|
51
|
+
dx1 = x_flat - self.x[0]
|
|
52
|
+
dx2 = x_flat - self.x[1]
|
|
53
|
+
|
|
54
|
+
norm_sq_1 = np.dot(dx1.T, dx1)
|
|
55
|
+
norm_sq_2 = np.dot(dx2.T, dx2)
|
|
56
|
+
|
|
57
|
+
denom = norm_sq_1 + norm_sq_2
|
|
58
|
+
|
|
59
|
+
if denom < 1e-12: # If points are very close (or identical)
|
|
60
|
+
return 0.5, 0.5
|
|
61
|
+
|
|
62
|
+
w1 = norm_sq_2 / denom
|
|
63
|
+
w2 = norm_sq_1 / denom
|
|
64
|
+
return w1, w2
|
|
65
|
+
|
|
66
|
+
def get_weight_grads(self, x_flat):
|
|
67
|
+
"""Calculate gradients of the weights ∇w1, ∇w2 at point x"""
|
|
68
|
+
dx1 = x_flat - self.x[0]
|
|
69
|
+
dx2 = x_flat - self.x[1]
|
|
70
|
+
|
|
71
|
+
n1 = np.dot(dx1.T, dx1) # |Δx1|^2
|
|
72
|
+
n2 = np.dot(dx2.T, dx2) # |Δx2|^2
|
|
73
|
+
d = n1 + n2
|
|
74
|
+
|
|
75
|
+
if d < 1e-12:
|
|
76
|
+
return np.zeros(self.dim), np.zeros(self.dim)
|
|
77
|
+
|
|
78
|
+
# ∇n1 = 2 * Δx1
|
|
79
|
+
# ∇n2 = 2 * Δx2
|
|
80
|
+
# ∇d = 2 * (Δx1 + Δx2)
|
|
81
|
+
grad_n1 = 2 * dx1
|
|
82
|
+
grad_n2 = 2 * dx2
|
|
83
|
+
grad_d = grad_n1 + grad_n2
|
|
84
|
+
|
|
85
|
+
# ∇w1 = ∇(n2 / d) = ( (∇n2) * d - n2 * (∇d) ) / d^2
|
|
86
|
+
grad_w1 = (grad_n2 * d - n2 * grad_d) / (d**2)
|
|
87
|
+
|
|
88
|
+
# ∇w2 = ∇(n1 / d) = ( (∇n1) * d - n1 * (∇d) ) / d^2
|
|
89
|
+
grad_w2 = (grad_n1 * d - n1 * grad_d) / (d**2)
|
|
90
|
+
|
|
91
|
+
return grad_w1, grad_w2
|
|
92
|
+
|
|
93
|
+
def get_energy(self, x_flat):
|
|
94
|
+
"""Calculate the energy E_DWI(x) on the DWI surface"""
|
|
95
|
+
w1, w2 = self.get_weights(x_flat)
|
|
96
|
+
t1 = self.get_taylor(0, x_flat)
|
|
97
|
+
t2 = self.get_taylor(1, x_flat)
|
|
98
|
+
return w1 * t1 + w2 * t2
|
|
99
|
+
|
|
100
|
+
def get_gradient(self, x_flat):
|
|
101
|
+
"""Calculate the gradient ∇E_DWI(x) on the DWI surface"""
|
|
102
|
+
w1, w2 = self.get_weights(x_flat)
|
|
103
|
+
gw1, gw2 = self.get_weight_grads(x_flat)
|
|
104
|
+
|
|
105
|
+
t1 = self.get_taylor(0, x_flat)
|
|
106
|
+
t2 = self.get_taylor(1, x_flat)
|
|
107
|
+
|
|
108
|
+
gt1 = self.get_taylor_grad(0, x_flat)
|
|
109
|
+
gt2 = self.get_taylor_grad(1, x_flat)
|
|
110
|
+
|
|
111
|
+
# ∇(w1*T1 + w2*T2) = (∇w1)T1 + w1(∇T1) + (∇w2)T2 + w2(∇T2)
|
|
112
|
+
grad = (gw1 * t1) + (w1 * gt1) + (gw2 * t2) + (w2 * gt2)
|
|
113
|
+
return grad.reshape(self.natoms, 3)
|
|
114
|
+
|
|
115
|
+
# --- Corrector Step for HPC ---
|
|
116
|
+
|
|
117
|
+
def corrector_step(dwi_surface, x_start, total_s, n_steps=100):
|
|
118
|
+
"""
|
|
119
|
+
Finds the corrected point x_corr by integrating on the DWI surface
|
|
120
|
+
using Euler's method.
|
|
121
|
+
dx/ds = -g / |g|
|
|
122
|
+
"""
|
|
123
|
+
h = total_s / n_steps # Arc length per step
|
|
124
|
+
x = x_start
|
|
125
|
+
|
|
126
|
+
for _ in range(n_steps):
|
|
127
|
+
g_flat = dwi_surface.get_gradient(x.flatten())
|
|
128
|
+
g = g_flat.flatten()
|
|
129
|
+
norm_g = np.linalg.norm(g)
|
|
130
|
+
|
|
131
|
+
if norm_g < 1e-9: # Reached a minimum
|
|
132
|
+
break
|
|
133
|
+
|
|
134
|
+
# Euler step
|
|
135
|
+
step_vec = - (g / norm_g) * h
|
|
136
|
+
x = x + step_vec.reshape(dwi_surface.natoms, 3)
|
|
137
|
+
|
|
138
|
+
return x
|
|
139
|
+
|
|
140
|
+
# --- Main HPC Class ---
|
|
141
|
+
|
|
142
|
+
class HPC:
|
|
143
|
+
"""Hessian-based Predictor-Corrector (HPC) method for IRC calculations
|
|
144
|
+
|
|
145
|
+
This class implements the HPC algorithm which uses
|
|
146
|
+
the LQA method as the predictor and a
|
|
147
|
+
DWI-based corrector.
|
|
148
|
+
|
|
149
|
+
References
|
|
150
|
+
----------
|
|
151
|
+
[1] J. Chem. Phys. 93, 5634–5642 (1990) (LQA)
|
|
152
|
+
[2] J. Chem. Phys. 120, 9918–9924 (2004) (HPC)
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
def __init__(self, element_list, electric_charge_and_multiplicity, FC_count, file_directory,
|
|
156
|
+
final_directory, force_data, max_step=1000, step_size=0.1, init_coord=None,
|
|
157
|
+
init_hess=None, calc_engine=None, xtb_method=None, **kwargs):
|
|
158
|
+
"""Initialize HPC IRC calculator"""
|
|
159
|
+
self.max_step = max_step
|
|
160
|
+
self.step_size = step_size
|
|
161
|
+
self.N_euler = 20000 # Number of Euler integration steps for LQA predictor
|
|
162
|
+
self.N_corrector = 100 # Number of Euler integration steps for DWI corrector
|
|
163
|
+
self.ModelHessianUpdate = ModelHessianUpdate()
|
|
164
|
+
self.CE = calc_engine
|
|
165
|
+
self.FC_count = FC_count
|
|
166
|
+
|
|
167
|
+
# initial condition
|
|
168
|
+
self.coords = init_coord
|
|
169
|
+
self.init_hess = init_hess # This is non-mass-weighted
|
|
170
|
+
self.mw_hessian = None # This is mass-weighted
|
|
171
|
+
self.xtb_method = xtb_method
|
|
172
|
+
|
|
173
|
+
# convergence criteria
|
|
174
|
+
self.MAX_FORCE_THRESHOLD = 0.0004
|
|
175
|
+
self.RMS_FORCE_THRESHOLD = 0.0001
|
|
176
|
+
|
|
177
|
+
self.element_list = element_list
|
|
178
|
+
self.electric_charge_and_multiplicity = electric_charge_and_multiplicity
|
|
179
|
+
self.directory = file_directory
|
|
180
|
+
self.final_directory = final_directory
|
|
181
|
+
self.force_data = force_data
|
|
182
|
+
|
|
183
|
+
# Previous step's data for HPC (non-mass-weighted, bias-inclusive)
|
|
184
|
+
self.prev_data = {
|
|
185
|
+
'coords': None,
|
|
186
|
+
'energy': None,
|
|
187
|
+
'gradient': None,
|
|
188
|
+
'hessian': None # (3N, 3N)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# Store only necessary data
|
|
192
|
+
self.irc_bias_energy_list = []
|
|
193
|
+
self.irc_energy_list = []
|
|
194
|
+
self.irc_mw_coords = []
|
|
195
|
+
self.irc_mw_gradients = []
|
|
196
|
+
self.path_bending_angle_list = []
|
|
197
|
+
|
|
198
|
+
# Create data files
|
|
199
|
+
self.create_csv_file()
|
|
200
|
+
self.create_xyz_file()
|
|
201
|
+
|
|
202
|
+
def create_csv_file(self):
|
|
203
|
+
"""Create CSV file for energy and gradient data"""
|
|
204
|
+
self.csv_filename = os.path.join(self.directory, "irc_energies_gradients.csv")
|
|
205
|
+
with open(self.csv_filename, 'w', newline='') as csvfile:
|
|
206
|
+
writer = csv.writer(csvfile)
|
|
207
|
+
writer.writerow(['Step', 'Energy (Hartree)', 'Bias Energy (Hartree)', 'RMS Gradient', 'RMS Bias Gradient'])
|
|
208
|
+
|
|
209
|
+
def create_xyz_file(self):
|
|
210
|
+
"""Create XYZ file for structure data"""
|
|
211
|
+
self.xyz_filename = os.path.join(self.directory, "irc_structures.xyz")
|
|
212
|
+
# Create empty file (will be appended to later)
|
|
213
|
+
open(self.xyz_filename, 'w').close()
|
|
214
|
+
|
|
215
|
+
def save_to_csv(self, step, energy, bias_energy, gradient, bias_gradient):
|
|
216
|
+
"""Save energy and gradient data to CSV file"""
|
|
217
|
+
rms_grad = np.sqrt((gradient**2).mean())
|
|
218
|
+
rms_bias_grad = np.sqrt((bias_gradient**2).mean())
|
|
219
|
+
|
|
220
|
+
with open(self.csv_filename, 'a', newline='') as csvfile:
|
|
221
|
+
writer = csv.writer(csvfile)
|
|
222
|
+
writer.writerow([step, energy, bias_energy, rms_grad, rms_bias_grad])
|
|
223
|
+
|
|
224
|
+
def save_xyz_structure(self, step, coords):
|
|
225
|
+
"""Save molecular structure to XYZ file"""
|
|
226
|
+
coords_angstrom = coords * UnitValueLib().bohr2angstroms
|
|
227
|
+
|
|
228
|
+
with open(self.xyz_filename, 'a') as f:
|
|
229
|
+
f.write(f"{len(coords)}\n")
|
|
230
|
+
f.write(f"IRC Step {step}\n")
|
|
231
|
+
for i, coord in enumerate(coords_angstrom):
|
|
232
|
+
f.write(f"{self.element_list[i]:<3} {coord[0]:15.10f} {coord[1]:15.10f} {coord[2]:15.10f}\n")
|
|
233
|
+
|
|
234
|
+
def get_mass_array(self):
|
|
235
|
+
"""Create arrays of atomic masses for mass-weighting operations"""
|
|
236
|
+
elem_mass_list = np.array([atomic_mass(elem) for elem in self.element_list], dtype="float64")
|
|
237
|
+
sqrt_mass_list = np.sqrt(elem_mass_list)
|
|
238
|
+
|
|
239
|
+
three_elem_mass_list = np.repeat(elem_mass_list, 3)
|
|
240
|
+
three_sqrt_mass_list = np.repeat(sqrt_mass_list, 3)
|
|
241
|
+
|
|
242
|
+
return elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list
|
|
243
|
+
|
|
244
|
+
def mass_weight_hessian(self, hessian, three_sqrt_mass_list):
|
|
245
|
+
"""Apply mass-weighting to the hessian matrix"""
|
|
246
|
+
mass_mat = np.diag(1.0 / three_sqrt_mass_list)
|
|
247
|
+
return np.dot(mass_mat, np.dot(hessian, mass_mat))
|
|
248
|
+
|
|
249
|
+
def mass_weight_coordinates(self, coordinates, sqrt_mass_list):
|
|
250
|
+
"""Convert coordinates to mass-weighted coordinates"""
|
|
251
|
+
mw_coords = copy.deepcopy(coordinates)
|
|
252
|
+
for i in range(len(coordinates)):
|
|
253
|
+
mw_coords[i] = coordinates[i] * sqrt_mass_list[i]
|
|
254
|
+
return mw_coords
|
|
255
|
+
|
|
256
|
+
def mass_weight_gradient(self, gradient, sqrt_mass_list):
|
|
257
|
+
"""Convert gradient to mass-weighted form"""
|
|
258
|
+
mw_gradient = copy.deepcopy(gradient)
|
|
259
|
+
for i in range(len(gradient)):
|
|
260
|
+
mw_gradient[i] = gradient[i] / sqrt_mass_list[i]
|
|
261
|
+
return mw_gradient
|
|
262
|
+
|
|
263
|
+
def unmass_weight_step(self, step, sqrt_mass_list):
|
|
264
|
+
"""Convert a step vector from mass-weighted to non-mass-weighted coordinates"""
|
|
265
|
+
unmw_step = copy.deepcopy(step)
|
|
266
|
+
for i in range(len(step)):
|
|
267
|
+
unmw_step[i] = step[i] / sqrt_mass_list[i]
|
|
268
|
+
return unmw_step
|
|
269
|
+
|
|
270
|
+
def check_energy_oscillation(self, energy_list):
|
|
271
|
+
"""Check if energy is oscillating (going up and down)"""
|
|
272
|
+
if len(energy_list) < 3:
|
|
273
|
+
return False
|
|
274
|
+
last_diff = energy_list[-1] - energy_list[-2]
|
|
275
|
+
prev_diff = energy_list[-2] - energy_list[-3]
|
|
276
|
+
return (last_diff * prev_diff) < 0
|
|
277
|
+
|
|
278
|
+
def step(self, mw_gradient, geom_num_list, mw_combined_hessian, sqrt_mass_list):
|
|
279
|
+
"""
|
|
280
|
+
Calculate a single LQA predictor step.
|
|
281
|
+
|
|
282
|
+
Note: This function receives the *combined* (model + bias)
|
|
283
|
+
mass-weighted Hessian.
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
# BFGS update (Hessian is updated in the run loop *before* calling this)
|
|
287
|
+
# self.mw_hessian is now the updated, combined, mass-weighted Hessian
|
|
288
|
+
|
|
289
|
+
eigenvalues, eigenvectors = np.linalg.eigh(mw_combined_hessian)
|
|
290
|
+
|
|
291
|
+
# Drop small eigenvalues
|
|
292
|
+
small_eigvals = np.abs(eigenvalues) < 1e-8
|
|
293
|
+
eigenvalues = eigenvalues[~small_eigvals]
|
|
294
|
+
eigenvectors = eigenvectors[:,~small_eigvals]
|
|
295
|
+
|
|
296
|
+
flattened_gradient = mw_gradient.flatten()
|
|
297
|
+
|
|
298
|
+
# --- MODIFICATION (Fix for numerical stability) ---
|
|
299
|
+
epsilon = 1e-6 # Prevent divergence when gradient norm is near zero
|
|
300
|
+
norm_g = np.linalg.norm(flattened_gradient)
|
|
301
|
+
dt = 1 / self.N_euler * self.step_size / max(norm_g, epsilon)
|
|
302
|
+
# --- END MODIFICATION ---
|
|
303
|
+
|
|
304
|
+
mw_gradient_proj = np.dot(eigenvectors.T, flattened_gradient)
|
|
305
|
+
|
|
306
|
+
# Integration of the step size
|
|
307
|
+
t = dt
|
|
308
|
+
current_length = 0
|
|
309
|
+
for j in range(self.N_euler):
|
|
310
|
+
dsdt = np.sqrt(np.sum(mw_gradient_proj**2 * np.exp(-2*eigenvalues*t)))
|
|
311
|
+
current_length += dsdt * dt
|
|
312
|
+
if current_length > self.step_size:
|
|
313
|
+
break
|
|
314
|
+
t += dt
|
|
315
|
+
|
|
316
|
+
# --- MODIFICATION (Fix for numerical stability) ---
|
|
317
|
+
x = -eigenvalues * t
|
|
318
|
+
small_x_mask = np.abs(x) < 1e-8
|
|
319
|
+
alphas = np.where(
|
|
320
|
+
small_x_mask,
|
|
321
|
+
-t,
|
|
322
|
+
np.expm1(x) / eigenvalues # Numerically stable (exp(x)-1)/eig
|
|
323
|
+
)
|
|
324
|
+
# --- END MODIFICATION ---
|
|
325
|
+
|
|
326
|
+
A = np.dot(eigenvectors, np.dot(np.diag(alphas), eigenvectors.T))
|
|
327
|
+
step = np.dot(A, flattened_gradient)
|
|
328
|
+
|
|
329
|
+
step = step.reshape(len(geom_num_list), 3)
|
|
330
|
+
step = self.unmass_weight_step(step, sqrt_mass_list)
|
|
331
|
+
|
|
332
|
+
new_geom = geom_num_list + step
|
|
333
|
+
new_geom -= Calculationtools().calc_center_of_mass(new_geom, self.element_list)
|
|
334
|
+
|
|
335
|
+
return new_geom
|
|
336
|
+
|
|
337
|
+
def run(self):
|
|
338
|
+
"""Run the HPC IRC calculation"""
|
|
339
|
+
print("Hessian-based Predictor-Corrector (HPC) method")
|
|
340
|
+
geom_num_list = self.coords # This is x_k_corr from previous step
|
|
341
|
+
CalcBiaspot = BiasPotentialCalculation(self.directory)
|
|
342
|
+
|
|
343
|
+
oscillation_counter = 0
|
|
344
|
+
|
|
345
|
+
# Get mass arrays for mass-weighting
|
|
346
|
+
elem_mass_list, sqrt_mass_list, three_elem_mass_list, three_sqrt_mass_list = self.get_mass_array()
|
|
347
|
+
|
|
348
|
+
# --- HPC ALGORITHM FLOW ---
|
|
349
|
+
# At start of loop 'iter', we are at point x_{k-1} (corrected)
|
|
350
|
+
# 1. (LQA Predictor) Use data(k-1) to predict x_k_pred
|
|
351
|
+
# 2. (Ab Initio) Calculate E, g, H at x_k_pred
|
|
352
|
+
# 3. (DWI Surface) Build DWI surface using data(k-1) and data(k_pred)
|
|
353
|
+
# 4. (Corrector) Integrate on DWI from x_{k-1} to get x_k_corr
|
|
354
|
+
# 5. (Store) Save x_k_corr data for next iteration
|
|
355
|
+
|
|
356
|
+
# --- Initialization (iter = 0) ---
|
|
357
|
+
# Perform calculation at the starting point (x_0)
|
|
358
|
+
print("# STEP: 0 (Initialization)")
|
|
359
|
+
e, g, geom_num_list, finish_frag = self.CE.single_point(
|
|
360
|
+
self.final_directory, self.element_list, 0,
|
|
361
|
+
self.electric_charge_and_multiplicity, self.xtb_method,
|
|
362
|
+
UnitValueLib().bohr2angstroms * geom_num_list
|
|
363
|
+
)
|
|
364
|
+
if finish_frag:
|
|
365
|
+
print("Initial calculation failed.")
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
_, B_e, B_g, BPA_hessian = CalcBiaspot.main(
|
|
369
|
+
e, g, geom_num_list, self.element_list,
|
|
370
|
+
self.force_data, g, 0, geom_num_list
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
# Get initial model Hessian
|
|
374
|
+
model_hessian = self.init_hess # Assumes init_hess is provided
|
|
375
|
+
model_hessian = Calculationtools().project_out_hess_tr_and_rot(
|
|
376
|
+
model_hessian, self.element_list, geom_num_list
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
# Store initial (k=0) data
|
|
380
|
+
self.prev_data = {
|
|
381
|
+
'coords': copy.deepcopy(geom_num_list),
|
|
382
|
+
'energy': B_e,
|
|
383
|
+
'gradient': copy.deepcopy(B_g),
|
|
384
|
+
'hessian': model_hessian + BPA_hessian, # non-MW, bias-inclusive
|
|
385
|
+
'bpa_hessian': BPA_hessian
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
# Save initial data to files
|
|
389
|
+
self.save_xyz_structure(0, geom_num_list)
|
|
390
|
+
self.save_to_csv(0, e, B_e, g, B_g)
|
|
391
|
+
|
|
392
|
+
# Store for BFGS/oscillation
|
|
393
|
+
self.irc_energy_list.append(e)
|
|
394
|
+
self.irc_bias_energy_list.append(B_e)
|
|
395
|
+
self.irc_mw_coords.append(self.mass_weight_coordinates(geom_num_list, sqrt_mass_list))
|
|
396
|
+
self.irc_mw_gradients.append(self.mass_weight_gradient(B_g, sqrt_mass_list))
|
|
397
|
+
|
|
398
|
+
# --- Main Integration Loop (iter = 1 to max_step) ---
|
|
399
|
+
for iter in range(1, self.max_step):
|
|
400
|
+
print(f"# STEP: {iter}")
|
|
401
|
+
exit_file_detect = os.path.exists(self.directory+"end.txt")
|
|
402
|
+
if exit_file_detect:
|
|
403
|
+
break
|
|
404
|
+
|
|
405
|
+
# --- 1. Predictor Step ---
|
|
406
|
+
# Get data from previous step (k-1) (corrected)
|
|
407
|
+
x_km1 = self.prev_data['coords']
|
|
408
|
+
e_km1 = self.prev_data['energy']
|
|
409
|
+
g_km1 = self.prev_data['gradient'] # non-MW, bias-inclusive
|
|
410
|
+
h_km1 = self.prev_data['hessian'] # non-MW, bias-inclusive
|
|
411
|
+
|
|
412
|
+
# Mass-weight and apply BFGS update
|
|
413
|
+
mw_g_km1 = self.mass_weight_gradient(g_km1, sqrt_mass_list)
|
|
414
|
+
mw_h_km1 = self.mass_weight_hessian(h_km1, three_sqrt_mass_list)
|
|
415
|
+
self.mw_hessian = mw_h_km1 # Start with last step's Hessian
|
|
416
|
+
|
|
417
|
+
if len(self.irc_mw_gradients) > 1:
|
|
418
|
+
delta_g = (self.irc_mw_gradients[-1] - self.irc_mw_gradients[-2]).reshape(-1, 1)
|
|
419
|
+
delta_x = (self.irc_mw_coords[-1] - self.irc_mw_coords[-2]).reshape(-1, 1)
|
|
420
|
+
if np.dot(delta_x.T, delta_g)[0, 0] > 1e-10:
|
|
421
|
+
delta_hess = self.ModelHessianUpdate.BFGS_hessian_update(self.mw_hessian, delta_x, delta_g)
|
|
422
|
+
self.mw_hessian += delta_hess
|
|
423
|
+
|
|
424
|
+
# Predict x_k_pred from x_km1 using LQA
|
|
425
|
+
x_k_pred = self.step(mw_g_km1, x_km1, self.mw_hessian, sqrt_mass_list)
|
|
426
|
+
|
|
427
|
+
# --- 2. Ab Initio Calculation at x_k_pred ---
|
|
428
|
+
e, g, x_k_pred_geom, finish_frag = self.CE.single_point(
|
|
429
|
+
self.final_directory, self.element_list, iter,
|
|
430
|
+
self.electric_charge_and_multiplicity, self.xtb_method,
|
|
431
|
+
UnitValueLib().bohr2angstroms * x_k_pred
|
|
432
|
+
)
|
|
433
|
+
if finish_frag: break
|
|
434
|
+
|
|
435
|
+
# Bias calculation at x_k_pred
|
|
436
|
+
_, e_k_pred, g_k_pred, h_bpa_k_pred = CalcBiaspot.main(
|
|
437
|
+
e, g, x_k_pred_geom, self.element_list,
|
|
438
|
+
self.force_data, g, iter, x_k_pred_geom
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
# Model Hessian calculation at x_k_pred
|
|
442
|
+
if iter % self.FC_count == 0:
|
|
443
|
+
model_h_k_pred = self.CE.Model_hess
|
|
444
|
+
else:
|
|
445
|
+
# Re-use the BFGS-updated Hessian (approximate)
|
|
446
|
+
# Convert mass-weighted back to non-mass-weighted
|
|
447
|
+
inv_mass_mat = np.diag(three_sqrt_mass_list)
|
|
448
|
+
combined_h_non_mw = np.dot(inv_mass_mat, np.dot(self.mw_hessian, inv_mass_mat))
|
|
449
|
+
model_h_k_pred = combined_h_non_mw - self.prev_data['bpa_hessian']
|
|
450
|
+
|
|
451
|
+
model_h_k_pred = Calculationtools().project_out_hess_tr_and_rot(
|
|
452
|
+
model_h_k_pred, self.element_list, x_k_pred_geom
|
|
453
|
+
)
|
|
454
|
+
h_k_pred = model_h_k_pred + h_bpa_k_pred # non-MW, bias-inclusive
|
|
455
|
+
|
|
456
|
+
# --- 3. Build DWI Surface ---
|
|
457
|
+
dwi = DWISurface(
|
|
458
|
+
x_km1, e_km1, g_km1, h_km1, # Point k-1
|
|
459
|
+
x_k_pred_geom, e_k_pred, g_k_pred, h_k_pred # Point k (predicted)
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# --- 4. Corrector Step ---
|
|
463
|
+
# Integrate on the DWI surface starting from x_km1
|
|
464
|
+
x_k_corr = corrector_step(dwi, x_km1, self.step_size, self.N_corrector)
|
|
465
|
+
|
|
466
|
+
# --- 5. Store and Save ---
|
|
467
|
+
geom_num_list = x_k_corr # This is the new starting point
|
|
468
|
+
self.save_xyz_structure(iter, x_k_corr) # Save the corrected structure
|
|
469
|
+
|
|
470
|
+
# Save the *ab initio* values from the *predicted* point to CSV
|
|
471
|
+
self.save_to_csv(iter, e, e_k_pred, g, g_k_pred)
|
|
472
|
+
|
|
473
|
+
# --- 6. Prepare for next step (k+1) ---
|
|
474
|
+
# Get the energy and gradient at the corrected point from the DWI surface
|
|
475
|
+
e_k_corr = dwi.get_energy(x_k_corr.flatten())
|
|
476
|
+
g_k_corr = dwi.get_gradient(x_k_corr.flatten())
|
|
477
|
+
|
|
478
|
+
# [cite_start]Per HPC paper, use the Hessian from the predicted end point [cite: 81]
|
|
479
|
+
h_k_corr = h_k_pred
|
|
480
|
+
|
|
481
|
+
self.prev_data = {
|
|
482
|
+
'coords': x_k_corr,
|
|
483
|
+
'energy': e_k_corr,
|
|
484
|
+
'gradient': g_k_corr,
|
|
485
|
+
'hessian': h_k_corr,
|
|
486
|
+
'bpa_hessian': h_bpa_k_pred # Save for next BFGS approx.
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
# --- Update Data History (for BFGS and oscillation) ---
|
|
490
|
+
if len(self.irc_energy_list) >= 3:
|
|
491
|
+
self.irc_energy_list.pop(0)
|
|
492
|
+
self.irc_bias_energy_list.pop(0)
|
|
493
|
+
self.irc_mw_coords.pop(0)
|
|
494
|
+
self.irc_mw_gradients.pop(0)
|
|
495
|
+
|
|
496
|
+
self.irc_energy_list.append(e) # ab initio E
|
|
497
|
+
self.irc_bias_energy_list.append(e_k_corr) # Corrected bias E
|
|
498
|
+
self.irc_mw_coords.append(self.mass_weight_coordinates(x_k_corr, sqrt_mass_list))
|
|
499
|
+
self.irc_mw_gradients.append(self.mass_weight_gradient(g_k_corr, sqrt_mass_list))
|
|
500
|
+
|
|
501
|
+
# --- Checks ---
|
|
502
|
+
if self.check_energy_oscillation(self.irc_bias_energy_list):
|
|
503
|
+
oscillation_counter += 1
|
|
504
|
+
print(f"Energy oscillation detected ({oscillation_counter}/5)")
|
|
505
|
+
if oscillation_counter >= 5:
|
|
506
|
+
print("Terminating IRC: Energy oscillated for 5 consecutive steps")
|
|
507
|
+
break
|
|
508
|
+
else:
|
|
509
|
+
oscillation_counter = 0
|
|
510
|
+
|
|
511
|
+
# Check convergence using the corrected gradient (DWI gradient)
|
|
512
|
+
if convergence_check(g_k_corr, self.MAX_FORCE_THRESHOLD, self.RMS_FORCE_THRESHOLD) and iter > 10:
|
|
513
|
+
print("Convergence reached. (HPC-IRC)")
|
|
514
|
+
break
|
|
515
|
+
|
|
516
|
+
# --- Common Output ---
|
|
517
|
+
|
|
518
|
+
# Calculate path bending angle
|
|
519
|
+
if iter > 1: # Needs at least 3 points
|
|
520
|
+
bend_angle = Calculationtools().calc_multi_dim_vec_angle(
|
|
521
|
+
self.irc_mw_coords[0]-self.irc_mw_coords[1],
|
|
522
|
+
self.irc_mw_coords[2]-self.irc_mw_coords[1]
|
|
523
|
+
)
|
|
524
|
+
self.path_bending_angle_list.append(np.degrees(bend_angle))
|
|
525
|
+
print(f"Path bending angle: {np.degrees(bend_angle):.4f}")
|
|
526
|
+
|
|
527
|
+
# Print current (corrected) geometry
|
|
528
|
+
print()
|
|
529
|
+
for i in range(len(geom_num_list)):
|
|
530
|
+
x = geom_num_list[i][0] * UnitValueLib().bohr2angstroms
|
|
531
|
+
y = geom_num_list[i][1] * UnitValueLib().bohr2angstroms
|
|
532
|
+
z = geom_num_list[i][2] * UnitValueLib().bohr2angstroms
|
|
533
|
+
print(f"{self.element_list[i]:<3} {x:17.12f} {y:17.12f} {z:17.12f}")
|
|
534
|
+
|
|
535
|
+
# Display information
|
|
536
|
+
print()
|
|
537
|
+
print("Energy (ab initio) : ", e)
|
|
538
|
+
print("Bias Energy (pred) : ", e_k_pred)
|
|
539
|
+
print("Bias Energy (corr) : ", e_k_corr)
|
|
540
|
+
print("RMS B. grad (pred) : ", np.sqrt((g_k_pred**2).mean()))
|
|
541
|
+
print("RMS B. grad (corr) : ", np.sqrt((g_k_corr**2).mean()))
|
|
542
|
+
print("-" * 30)
|
|
543
|
+
|
|
544
|
+
# Save final data visualization
|
|
545
|
+
G = Graph(self.directory)
|
|
546
|
+
rms_gradient_list = []
|
|
547
|
+
with open(self.csv_filename, 'r') as csvfile:
|
|
548
|
+
reader = csv.reader(csvfile)
|
|
549
|
+
next(reader) # Skip header
|
|
550
|
+
for row in reader:
|
|
551
|
+
rms_gradient_list.append(float(row[3]))
|
|
552
|
+
|
|
553
|
+
if self.path_bending_angle_list:
|
|
554
|
+
G.single_plot(
|
|
555
|
+
np.array(range(len(self.path_bending_angle_list))),
|
|
556
|
+
np.array(self.path_bending_angle_list),
|
|
557
|
+
self.directory,
|
|
558
|
+
atom_num=0,
|
|
559
|
+
axis_name_1="# STEP",
|
|
560
|
+
axis_name_2="bending angle [degrees]",
|
|
561
|
+
name="IRC_bending"
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
return
|