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
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from multioptpy.Potential.potential import BiasPotentialCalculation
|
|
6
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
7
|
+
from multioptpy.Visualization.visualization import Graph
|
|
8
|
+
|
|
9
|
+
class NewtonTrajectory:
|
|
10
|
+
"""
|
|
11
|
+
Implementation of the Growing Newton Trajectory (GNT) method for finding transition states.
|
|
12
|
+
|
|
13
|
+
Reference:
|
|
14
|
+
[1] Quapp, "Finding the Transition State without Initial Guess: The Growing
|
|
15
|
+
String Method for Newton Trajectory to Isomerization and Enantiomerization",
|
|
16
|
+
J. Comput. Chem. 2005, 26, 1383-1399, DOI: 10.1063/1.1885467
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, config):
|
|
19
|
+
self.config = config
|
|
20
|
+
self.step_len = config.gnt_step_len
|
|
21
|
+
self.rms_thresh = config.gnt_rms_thresh
|
|
22
|
+
self.gnt_vec = config.gnt_vec
|
|
23
|
+
self.micro_iter_limit = config.gnt_microiter
|
|
24
|
+
self.out_dir = Path(config.iEIP_FOLDER_DIRECTORY) if hasattr(Path, '__call__') else config.iEIP_FOLDER_DIRECTORY
|
|
25
|
+
|
|
26
|
+
# Initialize storage for trajectory data
|
|
27
|
+
self.images = []
|
|
28
|
+
self.all_energies = []
|
|
29
|
+
self.all_real_forces = []
|
|
30
|
+
self.sp_images = []
|
|
31
|
+
self.ts_images = []
|
|
32
|
+
self.min_images = []
|
|
33
|
+
self.ts_imag_freqs = []
|
|
34
|
+
|
|
35
|
+
# Flags for tracking stationary points
|
|
36
|
+
self.passed_min = False
|
|
37
|
+
self.passed_ts = False
|
|
38
|
+
self.did_reparametrization = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def rms(self, vector):
|
|
42
|
+
"""Calculate root mean square of a vector"""
|
|
43
|
+
return np.sqrt(np.mean(np.square(vector)))
|
|
44
|
+
|
|
45
|
+
def get_r(self, current_geom, final_geom=None):
|
|
46
|
+
"""Determine search direction vector"""
|
|
47
|
+
if final_geom is not None:
|
|
48
|
+
current_geom, _ = Calculationtools().kabsch_algorithm(current_geom, final_geom)
|
|
49
|
+
r = final_geom - current_geom
|
|
50
|
+
elif self.gnt_vec is not None:
|
|
51
|
+
# Parse atom indices from gnt_vec string
|
|
52
|
+
atom_indices = list(map(int, self.gnt_vec.split(",")))
|
|
53
|
+
if len(atom_indices) % 2 != 0:
|
|
54
|
+
raise ValueError("Invalid gnt_vec format. Need even number of atom indices.")
|
|
55
|
+
|
|
56
|
+
r = np.zeros_like(current_geom)
|
|
57
|
+
for i in range(len(atom_indices) // 2):
|
|
58
|
+
atom_i = atom_indices[2*i] - 1 # Convert to 0-indexed
|
|
59
|
+
atom_j = atom_indices[2*i+1] - 1
|
|
60
|
+
# Create a displacement vector between these atoms
|
|
61
|
+
r[atom_i] = current_geom[atom_j] - current_geom[atom_i]
|
|
62
|
+
r[atom_j] = current_geom[atom_i] - current_geom[atom_j]
|
|
63
|
+
else:
|
|
64
|
+
raise ValueError("Need to specify either final_geom or gnt_vec")
|
|
65
|
+
|
|
66
|
+
# Normalize the direction vector
|
|
67
|
+
r = r / np.linalg.norm(r)
|
|
68
|
+
return r
|
|
69
|
+
|
|
70
|
+
def calc_projector(self, r):
|
|
71
|
+
"""Calculate projector that keeps perpendicular component"""
|
|
72
|
+
flat_r = r.reshape(-1)
|
|
73
|
+
return np.eye(flat_r.size) - np.outer(flat_r, flat_r)
|
|
74
|
+
|
|
75
|
+
def grow_image(self, SP, FIO, geom, element_list, charge_multiplicity, r, iter_num, file_directory):
|
|
76
|
+
"""Grow a new image along the Newton trajectory"""
|
|
77
|
+
# Store current image
|
|
78
|
+
self.images.append(geom.copy())
|
|
79
|
+
|
|
80
|
+
# Calculate new displacement along r
|
|
81
|
+
step = self.step_len * r
|
|
82
|
+
new_geom = geom + step
|
|
83
|
+
|
|
84
|
+
# Prepare and run calculation at the new geometry
|
|
85
|
+
new_geom_tolist = (new_geom * self.config.bohr2angstroms).tolist()
|
|
86
|
+
for i, elem in enumerate(element_list):
|
|
87
|
+
new_geom_tolist[i].insert(0, elem)
|
|
88
|
+
|
|
89
|
+
new_geom_tolist.insert(0, charge_multiplicity)
|
|
90
|
+
|
|
91
|
+
file_directory = FIO.make_psi4_input_file([new_geom_tolist], iter_num)
|
|
92
|
+
energy, forces, geom_coords, error_flag = SP.single_point(
|
|
93
|
+
file_directory, element_list, iter_num, charge_multiplicity, self.config.force_data["xtb"]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if error_flag:
|
|
97
|
+
print("Error in QM calculation during trajectory growth.")
|
|
98
|
+
with open(os.path.join(self.out_dir, "end.txt"), "w") as f:
|
|
99
|
+
f.write("Error in QM calculation during trajectory growth.")
|
|
100
|
+
return None, None, None, True, None
|
|
101
|
+
|
|
102
|
+
# Store results
|
|
103
|
+
self.all_energies.append(energy)
|
|
104
|
+
self.all_real_forces.append(forces)
|
|
105
|
+
|
|
106
|
+
return energy, forces, geom_coords, False, file_directory
|
|
107
|
+
|
|
108
|
+
def initialize(self, SP, FIO, initial_geom, element_list, charge_multiplicity, file_directory, final_geom=None, iter_num=0):
|
|
109
|
+
"""Initialize the Newton trajectory
|
|
110
|
+
|
|
111
|
+
Parameters:
|
|
112
|
+
-----------
|
|
113
|
+
SP : SinglePoint
|
|
114
|
+
Object to perform single point calculations
|
|
115
|
+
FIO : FileIO
|
|
116
|
+
Object for file I/O operations
|
|
117
|
+
initial_geom : ndarray
|
|
118
|
+
Initial geometry coordinates
|
|
119
|
+
element_list : list
|
|
120
|
+
List of element symbols
|
|
121
|
+
charge_multiplicity : list
|
|
122
|
+
[charge, multiplicity]
|
|
123
|
+
file_directory : str
|
|
124
|
+
Path to current input file
|
|
125
|
+
final_geom : ndarray, optional
|
|
126
|
+
Final geometry coordinates
|
|
127
|
+
iter_num : int, optional
|
|
128
|
+
Current iteration number
|
|
129
|
+
"""
|
|
130
|
+
# Use the provided file_directory instead of trying to get it from FIO
|
|
131
|
+
energy, forces, geom_coords, error_flag = SP.single_point(
|
|
132
|
+
file_directory, element_list, iter_num, charge_multiplicity, self.config.force_data["xtb"]
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if error_flag:
|
|
136
|
+
print("Error in QM calculation during initialization.")
|
|
137
|
+
return None, None, True
|
|
138
|
+
|
|
139
|
+
# Store initial data
|
|
140
|
+
self.images.append(geom_coords.copy())
|
|
141
|
+
self.all_energies.append(energy)
|
|
142
|
+
self.all_real_forces.append(forces)
|
|
143
|
+
|
|
144
|
+
# Calculate search direction
|
|
145
|
+
self.r = self.get_r(geom_coords, final_geom)
|
|
146
|
+
self.r_org = self.r.copy()
|
|
147
|
+
|
|
148
|
+
# Calculate projector
|
|
149
|
+
self.P = self.calc_projector(self.r)
|
|
150
|
+
|
|
151
|
+
# Grow first image
|
|
152
|
+
energy, forces, geom_coords, error_flag, new_file_directory = self.grow_image(
|
|
153
|
+
SP, FIO, geom_coords, element_list, charge_multiplicity, self.r, iter_num, file_directory
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
return geom_coords, new_file_directory, error_flag
|
|
157
|
+
|
|
158
|
+
def optimize_frontier_image(self, SP, FIO, geom, element_list, charge_multiplicity, iter_num, file_directory):
|
|
159
|
+
"""Optimize the frontier image using projected forces"""
|
|
160
|
+
# Initialize BFGS variables
|
|
161
|
+
num_atoms = len(element_list)
|
|
162
|
+
num_coords = num_atoms * 3
|
|
163
|
+
H_inv = np.eye(num_coords) # Initial inverse Hessian approximation
|
|
164
|
+
prev_geom = None
|
|
165
|
+
prev_proj_grad = None
|
|
166
|
+
|
|
167
|
+
# Get current energy and forces - use provided file_directory
|
|
168
|
+
energy, forces, geom_coords, error_flag = SP.single_point(
|
|
169
|
+
file_directory, element_list, iter_num, charge_multiplicity, self.config.force_data["xtb"]
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if error_flag:
|
|
173
|
+
print("Error in QM calculation during frontier optimization.")
|
|
174
|
+
return None, None, None, True, None
|
|
175
|
+
|
|
176
|
+
# Main optimization loop
|
|
177
|
+
for micro_iter in range(self.micro_iter_limit):
|
|
178
|
+
# Project forces onto perpendicular space
|
|
179
|
+
flat_forces = forces.reshape(-1)
|
|
180
|
+
proj_forces = np.dot(self.P, flat_forces).reshape(geom_coords.shape)
|
|
181
|
+
|
|
182
|
+
# Calculate RMS of projected forces
|
|
183
|
+
proj_rms = self.rms(proj_forces)
|
|
184
|
+
|
|
185
|
+
if micro_iter % 5 == 0:
|
|
186
|
+
print(f"Micro-iteration {micro_iter}: Projected force RMS = {proj_rms:.6f}, Energy = {energy:.8f}")
|
|
187
|
+
|
|
188
|
+
# Check convergence
|
|
189
|
+
if proj_rms <= self.rms_thresh:
|
|
190
|
+
print(f"Frontier image converged after {micro_iter} micro-iterations")
|
|
191
|
+
break
|
|
192
|
+
|
|
193
|
+
# BFGS update
|
|
194
|
+
flat_geom = geom_coords.flatten()
|
|
195
|
+
flat_proj_forces = proj_forces.flatten()
|
|
196
|
+
|
|
197
|
+
if prev_geom is not None:
|
|
198
|
+
s = flat_geom - prev_geom # Position difference
|
|
199
|
+
y = prev_proj_grad - flat_proj_forces # Force difference (note: forces = -gradient)
|
|
200
|
+
|
|
201
|
+
# Check curvature condition
|
|
202
|
+
sy = np.dot(s, y)
|
|
203
|
+
if sy > 1e-10:
|
|
204
|
+
# BFGS update formula
|
|
205
|
+
rho = 1.0 / sy
|
|
206
|
+
V = np.eye(len(s)) - rho * np.outer(s, y)
|
|
207
|
+
H_inv = np.dot(V.T, np.dot(H_inv, V)) + rho * np.outer(s, s)
|
|
208
|
+
|
|
209
|
+
# Store current values for next iteration
|
|
210
|
+
prev_geom = flat_geom.copy()
|
|
211
|
+
prev_proj_grad = flat_proj_forces.copy()
|
|
212
|
+
|
|
213
|
+
# Calculate search direction
|
|
214
|
+
search_dir = -np.dot(H_inv, flat_proj_forces).reshape(geom_coords.shape)
|
|
215
|
+
|
|
216
|
+
# Determine step size (simple trust radius approach)
|
|
217
|
+
trust_radius = 0.02 # Bohr
|
|
218
|
+
step_norm = np.linalg.norm(search_dir)
|
|
219
|
+
if step_norm > trust_radius:
|
|
220
|
+
search_dir = search_dir * (trust_radius / step_norm)
|
|
221
|
+
|
|
222
|
+
# Update geometry
|
|
223
|
+
geom_coords = geom_coords + search_dir
|
|
224
|
+
|
|
225
|
+
# Prepare and run calculation at the new geometry
|
|
226
|
+
new_geom_tolist = (geom_coords * self.config.bohr2angstroms).tolist()
|
|
227
|
+
for i, elem in enumerate(element_list):
|
|
228
|
+
new_geom_tolist[i].insert(0, elem)
|
|
229
|
+
|
|
230
|
+
new_geom_tolist.insert(0, charge_multiplicity)
|
|
231
|
+
|
|
232
|
+
file_directory = FIO.make_psi4_input_file([new_geom_tolist], iter_num)
|
|
233
|
+
energy, forces, geom_coords, error_flag = SP.single_point(
|
|
234
|
+
file_directory, element_list, iter_num, charge_multiplicity, self.config.force_data["xtb"]
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
if error_flag:
|
|
238
|
+
print("Error in QM calculation during frontier optimization.")
|
|
239
|
+
return None, None, None, True, None
|
|
240
|
+
|
|
241
|
+
# Return optimized geometry
|
|
242
|
+
return energy, forces, geom_coords, False, file_directory
|
|
243
|
+
|
|
244
|
+
def reparametrize(self, SP, FIO, geom, element_list, charge_multiplicity, iter_num, file_directory):
|
|
245
|
+
"""Check if NT can be grown and update trajectory"""
|
|
246
|
+
# Get latest energy and forces
|
|
247
|
+
energy = self.all_energies[-1]
|
|
248
|
+
real_forces = self.all_real_forces[-1]
|
|
249
|
+
|
|
250
|
+
# Get projected forces
|
|
251
|
+
flat_forces = real_forces.reshape(-1)
|
|
252
|
+
proj_forces = np.dot(self.P, flat_forces).reshape(real_forces.shape)
|
|
253
|
+
|
|
254
|
+
# Check if we can grow the NT (convergence of frontier image)
|
|
255
|
+
proj_rms = self.rms(proj_forces)
|
|
256
|
+
can_grow = proj_rms <= self.rms_thresh
|
|
257
|
+
|
|
258
|
+
if can_grow:
|
|
259
|
+
|
|
260
|
+
# Check for stationary points
|
|
261
|
+
ae = self.all_energies
|
|
262
|
+
if len(ae) >= 3:
|
|
263
|
+
self.passed_min = ae[-3] > ae[-2] < ae[-1]
|
|
264
|
+
self.passed_ts = ae[-3] < ae[-2] > ae[-1]
|
|
265
|
+
|
|
266
|
+
if self.passed_min or self.passed_ts:
|
|
267
|
+
sp_image = self.images[-2].copy()
|
|
268
|
+
sp_kind = "Minimum" if self.passed_min else "TS"
|
|
269
|
+
self.sp_images.append(sp_image)
|
|
270
|
+
print(f"Passed stationary point! It seems to be a {sp_kind}.")
|
|
271
|
+
|
|
272
|
+
if self.passed_ts:
|
|
273
|
+
self.ts_images.append(sp_image)
|
|
274
|
+
# Calculate Hessian at TS if needed
|
|
275
|
+
# This would require additional implementation
|
|
276
|
+
elif self.passed_min:
|
|
277
|
+
self.min_images.append(sp_image)
|
|
278
|
+
|
|
279
|
+
# Update search direction if needed
|
|
280
|
+
r_new = self.get_r(geom)
|
|
281
|
+
r_dot = np.dot(r_new.reshape(-1), self.r.reshape(-1))
|
|
282
|
+
r_org_dot = np.dot(r_new.reshape(-1), self.r_org.reshape(-1))
|
|
283
|
+
print(f"r.dot(r')={r_dot:.6f} r_org.dot(r')={r_org_dot:.6f}")
|
|
284
|
+
|
|
285
|
+
# Update r if direction has changed significantly
|
|
286
|
+
if r_org_dot <= 0.5 and self.passed_min: # Using 0.5 as threshold
|
|
287
|
+
self.r = r_new
|
|
288
|
+
self.P = self.calc_projector(self.r)
|
|
289
|
+
print("Updated r")
|
|
290
|
+
|
|
291
|
+
# Grow new image
|
|
292
|
+
energy, forces, geom_coords, error_flag, new_file_directory = self.grow_image(
|
|
293
|
+
SP, FIO, geom, element_list, charge_multiplicity, self.r, iter_num, file_directory
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
if error_flag:
|
|
297
|
+
return None, True, None
|
|
298
|
+
|
|
299
|
+
self.did_reparametrization = True
|
|
300
|
+
return geom_coords, False, new_file_directory
|
|
301
|
+
else:
|
|
302
|
+
# Optimize frontier image since it's not converged yet
|
|
303
|
+
energy, forces, geom_coords, error_flag, new_file_directory = self.optimize_frontier_image(
|
|
304
|
+
SP, FIO, geom, element_list, charge_multiplicity, iter_num, file_directory
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if error_flag:
|
|
308
|
+
return None, True, None
|
|
309
|
+
|
|
310
|
+
# Update stored energy and forces
|
|
311
|
+
self.all_energies[-1] = energy
|
|
312
|
+
self.all_real_forces[-1] = forces
|
|
313
|
+
|
|
314
|
+
self.did_reparametrization = False
|
|
315
|
+
return geom_coords, False, new_file_directory
|
|
316
|
+
|
|
317
|
+
def check_convergence(self):
|
|
318
|
+
"""Check if the Newton Trajectory calculation has converged"""
|
|
319
|
+
if len(self.ts_images) == 0:
|
|
320
|
+
return False
|
|
321
|
+
|
|
322
|
+
# Consider converged if we've found a TS
|
|
323
|
+
return True
|
|
324
|
+
|
|
325
|
+
def get_additional_print(self):
|
|
326
|
+
"""Get additional information for printing"""
|
|
327
|
+
if self.did_reparametrization:
|
|
328
|
+
img_num = len(self.images)
|
|
329
|
+
str_ = f"Grew Newton trajectory to {img_num} images."
|
|
330
|
+
if self.passed_min:
|
|
331
|
+
str_ += f" Passed minimum geometry at image {img_num-1}."
|
|
332
|
+
elif self.passed_ts:
|
|
333
|
+
str_ += f" Passed transition state geometry at image {img_num-1}."
|
|
334
|
+
else:
|
|
335
|
+
str_ = None
|
|
336
|
+
|
|
337
|
+
# Reset flags
|
|
338
|
+
self.did_reparametrization = False
|
|
339
|
+
self.passed_min = False
|
|
340
|
+
self.passed_ts = False
|
|
341
|
+
|
|
342
|
+
return str_
|
|
343
|
+
|
|
344
|
+
def main(self, file_directory_1, file_directory_2, SP1, SP2, element_list, init_electric_charge_and_multiplicity, final_electric_charge_and_multiplicity, FIO1, FIO2):
|
|
345
|
+
"""Main method to run Newton Trajectory calculation"""
|
|
346
|
+
G = Graph(self.config.iEIP_FOLDER_DIRECTORY)
|
|
347
|
+
BIAS_GRAD_LIST_A = []
|
|
348
|
+
BIAS_ENERGY_LIST_A = []
|
|
349
|
+
GRAD_LIST_A = []
|
|
350
|
+
ENERGY_LIST_A = []
|
|
351
|
+
|
|
352
|
+
# Get initial geometry from first file
|
|
353
|
+
energy_1, gradient_1, geom_num_list_1, error_flag_1 = SP1.single_point(
|
|
354
|
+
file_directory_1, element_list, 0, init_electric_charge_and_multiplicity, self.config.force_data["xtb"]
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
if error_flag_1:
|
|
358
|
+
print("Error in initial QM calculation.")
|
|
359
|
+
with open(os.path.join(self.config.iEIP_FOLDER_DIRECTORY, "end.txt"), "w") as f:
|
|
360
|
+
f.write("Error in initial QM calculation.")
|
|
361
|
+
return
|
|
362
|
+
|
|
363
|
+
# If using final geometry for direction, get it
|
|
364
|
+
final_geom = None
|
|
365
|
+
if self.gnt_vec is None:
|
|
366
|
+
energy_2, gradient_2, geom_num_list_2, error_flag_2 = SP2.single_point(
|
|
367
|
+
file_directory_2, element_list, 0, final_electric_charge_and_multiplicity, self.config.force_data["xtb"]
|
|
368
|
+
)
|
|
369
|
+
if error_flag_2:
|
|
370
|
+
print("Error in second QM calculation.")
|
|
371
|
+
with open(os.path.join(self.config.iEIP_FOLDER_DIRECTORY, "end.txt"), "w") as f:
|
|
372
|
+
f.write("Error in second QM calculation.")
|
|
373
|
+
return
|
|
374
|
+
final_geom = geom_num_list_2
|
|
375
|
+
|
|
376
|
+
# Initialize Newton trajectory
|
|
377
|
+
geom, file_directory, error_flag = self.initialize(
|
|
378
|
+
SP1, FIO1, geom_num_list_1, element_list, init_electric_charge_and_multiplicity, file_directory_1, final_geom
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
if error_flag:
|
|
382
|
+
return
|
|
383
|
+
|
|
384
|
+
# Main iteration loop
|
|
385
|
+
for iter in range(1, self.config.microiterlimit):
|
|
386
|
+
print(f"==========================================================")
|
|
387
|
+
print(f"Newton Trajectory Iteration ({iter}/{self.config.microiterlimit})")
|
|
388
|
+
|
|
389
|
+
# Check for early termination
|
|
390
|
+
if os.path.isfile(os.path.join(self.config.iEIP_FOLDER_DIRECTORY, "end.txt")):
|
|
391
|
+
break
|
|
392
|
+
|
|
393
|
+
# Grow trajectory or optimize frontier image
|
|
394
|
+
geom, error_flag, file_directory = self.reparametrize(
|
|
395
|
+
SP1, FIO1, geom, element_list, init_electric_charge_and_multiplicity, iter, file_directory
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
if error_flag:
|
|
399
|
+
break
|
|
400
|
+
|
|
401
|
+
# Get current energy and forces
|
|
402
|
+
energy = self.all_energies[-1]
|
|
403
|
+
forces = self.all_real_forces[-1]
|
|
404
|
+
|
|
405
|
+
# Calculate bias potential if needed
|
|
406
|
+
BPC = BiasPotentialCalculation(self.config.iEIP_FOLDER_DIRECTORY)
|
|
407
|
+
_, bias_energy, bias_gradient, _ = BPC.main(
|
|
408
|
+
energy, forces, geom, element_list, self.config.force_data
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
# Record data for plotting
|
|
412
|
+
ENERGY_LIST_A.append(energy * self.config.hartree2kcalmol)
|
|
413
|
+
GRAD_LIST_A.append(np.sqrt(np.sum(forces**2)))
|
|
414
|
+
BIAS_ENERGY_LIST_A.append(bias_energy * self.config.hartree2kcalmol)
|
|
415
|
+
BIAS_GRAD_LIST_A.append(np.sqrt(np.sum(bias_gradient**2)))
|
|
416
|
+
|
|
417
|
+
# Print current status
|
|
418
|
+
add_info = self.get_additional_print() or ""
|
|
419
|
+
print(f"Energy : {energy}")
|
|
420
|
+
print(f"Bias Energy : {bias_energy}")
|
|
421
|
+
print(f"Gradient Norm : {np.linalg.norm(forces)}")
|
|
422
|
+
print(f"Bias Gradient Norm : {np.linalg.norm(bias_gradient)}")
|
|
423
|
+
print(add_info)
|
|
424
|
+
print(f"==========================================================")
|
|
425
|
+
|
|
426
|
+
# Check for convergence
|
|
427
|
+
if self.check_convergence():
|
|
428
|
+
print("Newton Trajectory converged to transition state!")
|
|
429
|
+
break
|
|
430
|
+
|
|
431
|
+
else:
|
|
432
|
+
print("Reached maximum number of iterations. Newton trajectory calculation completed.")
|
|
433
|
+
|
|
434
|
+
# Create energy and gradient profile plots
|
|
435
|
+
NUM_LIST = list(range(len(ENERGY_LIST_A)))
|
|
436
|
+
|
|
437
|
+
G.single_plot(NUM_LIST, ENERGY_LIST_A, file_directory_1, "energy",
|
|
438
|
+
axis_name_2="energy [kcal/mol]", name="nt_energy")
|
|
439
|
+
G.single_plot(NUM_LIST, GRAD_LIST_A, file_directory_1, "gradient",
|
|
440
|
+
axis_name_2="grad (RMS) [a.u.]", name="nt_gradient")
|
|
441
|
+
G.single_plot(NUM_LIST, BIAS_ENERGY_LIST_A, file_directory_1, "bias_energy",
|
|
442
|
+
axis_name_2="energy [kcal/mol]", name="nt_bias_energy")
|
|
443
|
+
G.single_plot(NUM_LIST, BIAS_GRAD_LIST_A, file_directory_1, "bias_gradient",
|
|
444
|
+
axis_name_2="grad (RMS) [a.u.]", name="nt_bias_gradient")
|
|
445
|
+
|
|
446
|
+
# Create trajectory file
|
|
447
|
+
FIO1.make_traj_file_for_DM(img_1="A", img_2="B")
|
|
448
|
+
|
|
449
|
+
# Identify critical points
|
|
450
|
+
FIO1.argrelextrema_txt_save(ENERGY_LIST_A, "approx_TS", "max")
|
|
451
|
+
FIO1.argrelextrema_txt_save(ENERGY_LIST_A, "approx_EQ", "min")
|
|
452
|
+
FIO1.argrelextrema_txt_save(GRAD_LIST_A, "local_min_grad", "min")
|
|
453
|
+
|
|
454
|
+
return
|