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/neb.py
ADDED
|
@@ -0,0 +1,1267 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
import sys
|
|
4
|
+
import glob
|
|
5
|
+
import datetime
|
|
6
|
+
import copy
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
import psi4
|
|
11
|
+
except:
|
|
12
|
+
psi4 = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
import pyscf
|
|
17
|
+
from pyscf import tdscf
|
|
18
|
+
from pyscf.hessian import thermo
|
|
19
|
+
except:
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
import dxtb
|
|
24
|
+
dxtb.timer.disable()
|
|
25
|
+
except:
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
from scipy.signal import argrelmax
|
|
29
|
+
|
|
30
|
+
from multioptpy.interface import force_data_parser
|
|
31
|
+
from multioptpy.Parameters.parameter import element_number
|
|
32
|
+
from multioptpy.Potential.potential import BiasPotentialCalculation
|
|
33
|
+
from multioptpy.MEP.pathopt_bneb_force import CaluculationBNEB, CaluculationBNEB2, CaluculationBNEB3
|
|
34
|
+
from multioptpy.MEP.pathopt_dneb_force import CaluculationDNEB
|
|
35
|
+
from multioptpy.MEP.pathopt_dmf_force import CaluculationDMF
|
|
36
|
+
from multioptpy.MEP.pathopt_nesb_force import CaluculationNESB
|
|
37
|
+
from multioptpy.MEP.pathopt_lup_force import CaluculationLUP
|
|
38
|
+
from multioptpy.MEP.pathopt_om_force import CaluculationOM
|
|
39
|
+
from multioptpy.MEP.pathopt_ewbneb_force import CaluculationEWBNEB
|
|
40
|
+
from multioptpy.MEP.pathopt_qsm_force import CaluculationQSM
|
|
41
|
+
from multioptpy.MEP.pathopt_qsmv2_force import CaluculationQSMv2
|
|
42
|
+
from multioptpy.Utils.calc_tools import Calculationtools
|
|
43
|
+
from multioptpy.Potential.idpp import IDPP, CFB_ENM
|
|
44
|
+
from multioptpy.Constraint.constraint_condition import ProjectOutConstrain
|
|
45
|
+
from multioptpy.fileio import xyz2list, traj2list, FileIO
|
|
46
|
+
from multioptpy.Optimizer import lbfgs_neb
|
|
47
|
+
from multioptpy.Optimizer import conjugate_gradient_neb
|
|
48
|
+
from multioptpy.Optimizer import trust_radius_neb
|
|
49
|
+
from multioptpy.Optimizer.fire_neb import FIREOptimizer
|
|
50
|
+
from multioptpy.Optimizer.rfo_neb import RFOOptimizer, RFOQSMOptimizer
|
|
51
|
+
from multioptpy.Optimizer.gradientdescent_neb import SteepestDescentOptimizer
|
|
52
|
+
from multioptpy.ModelHessian.approx_hessian import ApproxHessian
|
|
53
|
+
from multioptpy.Visualization.visualization import NEBVisualizer
|
|
54
|
+
from multioptpy.Calculator.tblite_calculation_tools import TBLiteEngine
|
|
55
|
+
from multioptpy.Calculator.pyscf_calculation_tools import PySCFEngine
|
|
56
|
+
from multioptpy.Calculator.psi4_calculation_tools import Psi4Engine
|
|
57
|
+
from multioptpy.Calculator.dxtb_calculation_tools import DXTBEngine
|
|
58
|
+
from multioptpy.Calculator.ase_calculation_tools import ASEEngine
|
|
59
|
+
from multioptpy.Calculator.sqm1_calculation_tools import SQM1Engine
|
|
60
|
+
from multioptpy.Calculator.sqm2_calculation_tools import SQM2Engine
|
|
61
|
+
from multioptpy.Calculator.lj_calculation_tools import LJEngine
|
|
62
|
+
from multioptpy.Calculator.emt_calculation_tools import EMTEngine
|
|
63
|
+
from multioptpy.Calculator.tersoff_calculation_tools import TersoffEngine
|
|
64
|
+
from multioptpy.Utils.calc_tools import apply_climbing_image, calc_path_length_list
|
|
65
|
+
from multioptpy.Interpolation.geodesic_interpolation import distribute_geometry_geodesic
|
|
66
|
+
from multioptpy.Interpolation.binomial_interpolation import bernstein_interpolation, distribute_geometry_by_length_bernstein, distribute_geometry_by_energy_bernstein
|
|
67
|
+
from multioptpy.Interpolation.spline_interpolation import spline_interpolation, distribute_geometry_spline, distribute_geometry_by_length_spline
|
|
68
|
+
from multioptpy.Interpolation.linear_interpolation import distribute_geometry, distribute_geometry_by_length, distribute_geometry_by_energy, distribute_geometry_by_predicted_energy
|
|
69
|
+
from multioptpy.Interpolation.savitzky_golay_interpolation import savitzky_golay_interpolation, distribute_geometry_by_length_savgol
|
|
70
|
+
from multioptpy.Interpolation.adaptive_interpolation import adaptive_geometry_energy_interpolation
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class NEBConfig:
|
|
75
|
+
"""Configuration management class for NEB calculations"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, args):
|
|
78
|
+
# Basic calculation settings
|
|
79
|
+
self.functional = args.functional
|
|
80
|
+
self.basisset = args.basisset
|
|
81
|
+
self.BASIS_SET = args.basisset
|
|
82
|
+
self.basic_set_and_function = args.functional + "/" + args.basisset
|
|
83
|
+
self.FUNCTIONAL = args.functional
|
|
84
|
+
|
|
85
|
+
# Solvent model settings
|
|
86
|
+
self.cpcm_solv_model = args.cpcm_solv_model
|
|
87
|
+
self.alpb_solv_model = args.alpb_solv_model
|
|
88
|
+
|
|
89
|
+
# Computational settings
|
|
90
|
+
self.N_THREAD = args.N_THREAD
|
|
91
|
+
self.SET_MEMORY = args.SET_MEMORY
|
|
92
|
+
self.pyscf = args.pyscf
|
|
93
|
+
self.usextb = args.usextb
|
|
94
|
+
self.usedxtb = args.usedxtb
|
|
95
|
+
self.sqm1 = args.sqm1
|
|
96
|
+
self.sqm2 = args.sqm2
|
|
97
|
+
|
|
98
|
+
# NEB specific settings
|
|
99
|
+
self.NEB_NUM = args.NSTEP
|
|
100
|
+
self.partition = args.partition
|
|
101
|
+
self.APPLY_CI_NEB = args.apply_CI_NEB
|
|
102
|
+
self.om = args.OM
|
|
103
|
+
self.lup = args.LUP
|
|
104
|
+
self.dneb = args.DNEB
|
|
105
|
+
self.nesb = args.NESB
|
|
106
|
+
self.bneb = args.BNEB
|
|
107
|
+
self.bneb2 = args.BNEB2
|
|
108
|
+
self.ewbneb = args.EWBNEB
|
|
109
|
+
self.dmf = args.DMF
|
|
110
|
+
self.qsm = args.QSM
|
|
111
|
+
self.qsmv2 = args.QSMv2
|
|
112
|
+
tmp_aneb = args.ANEB
|
|
113
|
+
|
|
114
|
+
if tmp_aneb is None:
|
|
115
|
+
self.aneb_flag = False
|
|
116
|
+
self.aneb_interpolation_num = 0
|
|
117
|
+
self.aneb_frequency = 100000000000000000 # approximate infinite number
|
|
118
|
+
|
|
119
|
+
elif len(tmp_aneb) == 2:
|
|
120
|
+
self.aneb_flag = True
|
|
121
|
+
self.aneb_interpolation_num = int(tmp_aneb[0])
|
|
122
|
+
self.aneb_frequency = int(tmp_aneb[1])
|
|
123
|
+
if self.aneb_frequency < 1 or self.aneb_interpolation_num < 1:
|
|
124
|
+
print("invalid input (-aneb)")
|
|
125
|
+
print("Recommended setting is applied.")
|
|
126
|
+
self.aneb_interpolation_num = 1
|
|
127
|
+
self.aneb_frequency = 1
|
|
128
|
+
else:
|
|
129
|
+
self.aneb_flag = False
|
|
130
|
+
self.aneb_interpolation_num = 0
|
|
131
|
+
self.aneb_frequency = 100000000000000000 # approximate infinite number
|
|
132
|
+
print("invalid input (-aneb)")
|
|
133
|
+
exit()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# Optimization settings
|
|
137
|
+
self.FC_COUNT = args.calc_exact_hess
|
|
138
|
+
self.MFC_COUNT = int(args.calc_model_hess)
|
|
139
|
+
self.model_hessian = args.use_model_hessian
|
|
140
|
+
self.climbing_image_start = args.climbing_image[0]
|
|
141
|
+
self.climbing_image_interval = args.climbing_image[1]
|
|
142
|
+
self.sd = args.steepest_descent
|
|
143
|
+
self.cg_method = args.conjugate_gradient
|
|
144
|
+
self.lbfgs_method = args.memory_limited_BFGS
|
|
145
|
+
|
|
146
|
+
# Flags
|
|
147
|
+
self.IDPP_flag = args.use_image_dependent_pair_potential
|
|
148
|
+
self.CFB_ENM_flag = args.use_correlated_flat_bottom_elastic_network_model
|
|
149
|
+
self.align_distances = args.align_distances
|
|
150
|
+
self.align_distances_energy = args.align_distances_energy
|
|
151
|
+
self.align_distances_energy_predicted = args.align_distances_energy_predicted
|
|
152
|
+
self.align_distances_spline = args.align_distances_spline
|
|
153
|
+
self.align_distances_spline_ver2 = args.align_distances_spline_ver2
|
|
154
|
+
self.align_distances_geodesic = args.align_distances_geodesic
|
|
155
|
+
self.align_distances_bernstein = args.align_distances_bernstein
|
|
156
|
+
self.align_distances_bernstein_energy = args.align_distances_bernstein_energy
|
|
157
|
+
self.align_distances_adaptive_energy = args.align_distances_adaptive_energy
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
tmp_align_savgol_list = args.align_distances_savgol.split(",")
|
|
162
|
+
|
|
163
|
+
if len(tmp_align_savgol_list) != 3:
|
|
164
|
+
print("invalid input (-adsg)")
|
|
165
|
+
exit()
|
|
166
|
+
else:
|
|
167
|
+
self.align_distances_savgol = int(tmp_align_savgol_list[0])
|
|
168
|
+
self.align_distances_savgol_window = int(tmp_align_savgol_list[1])
|
|
169
|
+
self.align_distances_savgol_poly = int(tmp_align_savgol_list[2])
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
self.node_distance_spline = args.node_distance_spline
|
|
173
|
+
self.node_distance_bernstein = args.node_distance_bernstein
|
|
174
|
+
if args.node_distance_savgol is None:
|
|
175
|
+
self.node_distance_savgol = None
|
|
176
|
+
self.node_distance_savgol_window = 0
|
|
177
|
+
self.node_distance_savgol_poly = 0
|
|
178
|
+
else:
|
|
179
|
+
tmp_node_savgol = args.node_distance_savgol.split(",")
|
|
180
|
+
if len(tmp_node_savgol) != 3:
|
|
181
|
+
print("invalid input (-node_distance_savgol)")
|
|
182
|
+
exit()
|
|
183
|
+
self.node_distance_savgol = float(tmp_node_savgol[0])
|
|
184
|
+
self.node_distance_savgol_window = int(tmp_node_savgol[1])
|
|
185
|
+
self.node_distance_savgol_poly = int(tmp_node_savgol[2])
|
|
186
|
+
|
|
187
|
+
self.excited_state = args.excited_state
|
|
188
|
+
self.unrestrict = args.unrestrict
|
|
189
|
+
self.save_pict = args.save_pict
|
|
190
|
+
self.apply_convergence_criteria = args.apply_convergence_criteria
|
|
191
|
+
self.node_distance = args.node_distance
|
|
192
|
+
self.not_ts_optimization = args.not_ts_optimization
|
|
193
|
+
|
|
194
|
+
# Electronic state settings
|
|
195
|
+
self.electronic_charge = args.electronic_charge
|
|
196
|
+
self.spin_multiplicity = args.spin_multiplicity
|
|
197
|
+
|
|
198
|
+
# Constants
|
|
199
|
+
self.bohr2angstroms = 0.52917721067
|
|
200
|
+
self.hartree2kcalmol = 627.509
|
|
201
|
+
|
|
202
|
+
# Additional settings
|
|
203
|
+
self.dft_grid = int(args.dft_grid)
|
|
204
|
+
self.spring_constant_k = 0.01
|
|
205
|
+
self.force_const_for_cineb = 0.01
|
|
206
|
+
self.othersoft = args.othersoft
|
|
207
|
+
self.software_path_file = args.software_path_file
|
|
208
|
+
self.ratio_of_rfo_step = args.ratio_of_rfo_step
|
|
209
|
+
|
|
210
|
+
# FIRE method parameters
|
|
211
|
+
self.FIRE_dt = 0.1
|
|
212
|
+
self.dt = 0.5
|
|
213
|
+
self.a = 0.10
|
|
214
|
+
self.n_reset = 0
|
|
215
|
+
self.FIRE_N_accelerate = 5
|
|
216
|
+
self.FIRE_f_inc = 1.10
|
|
217
|
+
self.FIRE_f_accelerate = 0.99
|
|
218
|
+
self.FIRE_f_decelerate = 0.5
|
|
219
|
+
self.FIRE_a_start = 0.1
|
|
220
|
+
self.FIRE_dt_max = 1.0
|
|
221
|
+
|
|
222
|
+
# Initialize derived settings
|
|
223
|
+
self.set_sub_basisset(args)
|
|
224
|
+
self.set_fixed_edges(args)
|
|
225
|
+
|
|
226
|
+
# Input file and directory settings
|
|
227
|
+
self.init_input = args.JOB
|
|
228
|
+
self.NEB_FOLDER_DIRECTORY = self.make_neb_work_directory(args.JOB)
|
|
229
|
+
|
|
230
|
+
def set_sub_basisset(self, args):
|
|
231
|
+
"""Set up basis set configuration"""
|
|
232
|
+
if len(args.sub_basisset) % 2 != 0:
|
|
233
|
+
print("invalid input (-sub_bs)")
|
|
234
|
+
sys.exit(0)
|
|
235
|
+
|
|
236
|
+
if args.pyscf:
|
|
237
|
+
self.SUB_BASIS_SET = {}
|
|
238
|
+
if len(args.sub_basisset) > 0:
|
|
239
|
+
self.SUB_BASIS_SET["default"] = str(args.basisset)
|
|
240
|
+
for j in range(int(len(args.sub_basisset)/2)):
|
|
241
|
+
self.SUB_BASIS_SET[args.sub_basisset[2*j]] = args.sub_basisset[2*j+1]
|
|
242
|
+
print("Basis Sets defined by User are detected.")
|
|
243
|
+
print(self.SUB_BASIS_SET)
|
|
244
|
+
else:
|
|
245
|
+
self.SUB_BASIS_SET = {"default": args.basisset}
|
|
246
|
+
else:
|
|
247
|
+
self.SUB_BASIS_SET = args.basisset
|
|
248
|
+
if len(args.sub_basisset) > 0:
|
|
249
|
+
self.SUB_BASIS_SET += "\nassign " + str(args.basisset) + "\n"
|
|
250
|
+
for j in range(int(len(args.sub_basisset)/2)):
|
|
251
|
+
self.SUB_BASIS_SET += "assign " + args.sub_basisset[2*j] + " " + args.sub_basisset[2*j+1] + "\n"
|
|
252
|
+
print("Basis Sets defined by User are detected.")
|
|
253
|
+
print(self.SUB_BASIS_SET)
|
|
254
|
+
|
|
255
|
+
# ECP settings
|
|
256
|
+
if len(args.effective_core_potential) % 2 != 0:
|
|
257
|
+
print("invalid input (-ecp)")
|
|
258
|
+
sys.exit(0)
|
|
259
|
+
|
|
260
|
+
if args.pyscf:
|
|
261
|
+
self.ECP = {}
|
|
262
|
+
if len(args.effective_core_potential) > 0:
|
|
263
|
+
for j in range(int(len(args.effective_core_potential)/2)):
|
|
264
|
+
self.ECP[args.effective_core_potential[2*j]] = args.effective_core_potential[2*j+1]
|
|
265
|
+
else:
|
|
266
|
+
self.ECP = ""
|
|
267
|
+
|
|
268
|
+
def set_fixed_edges(self, args):
|
|
269
|
+
"""Set up edge fixing configuration"""
|
|
270
|
+
if args.fixedges <= 0:
|
|
271
|
+
self.fix_init_edge = False
|
|
272
|
+
self.fix_end_edge = False
|
|
273
|
+
elif args.fixedges == 1:
|
|
274
|
+
self.fix_init_edge = True
|
|
275
|
+
self.fix_end_edge = False
|
|
276
|
+
elif args.fixedges == 2:
|
|
277
|
+
self.fix_init_edge = False
|
|
278
|
+
self.fix_end_edge = True
|
|
279
|
+
else:
|
|
280
|
+
self.fix_init_edge = True
|
|
281
|
+
self.fix_end_edge = True
|
|
282
|
+
|
|
283
|
+
def make_neb_work_directory(self, input_file):
|
|
284
|
+
"""Create NEB working directory path"""
|
|
285
|
+
if os.path.splitext(input_file)[1] == ".xyz":
|
|
286
|
+
tmp_name = os.path.splitext(input_file)[0]
|
|
287
|
+
else:
|
|
288
|
+
tmp_name = input_file
|
|
289
|
+
|
|
290
|
+
timestamp = str(datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f")[:-2])
|
|
291
|
+
if self.othersoft != "None":
|
|
292
|
+
return tmp_name + "_NEB_" + self.othersoft + "_" + timestamp + "/"
|
|
293
|
+
elif self.sqm2:
|
|
294
|
+
return tmp_name + "_NEB_SQM2_" + timestamp + "/"
|
|
295
|
+
|
|
296
|
+
elif self.sqm1:
|
|
297
|
+
return tmp_name + "_NEB_SQM1_" + timestamp + "/"
|
|
298
|
+
|
|
299
|
+
elif self.usextb == "None" and self.usedxtb == "None":
|
|
300
|
+
return tmp_name + "_NEB_" + self.basic_set_and_function.replace("/", "_") + "_" + timestamp + "/"
|
|
301
|
+
else:
|
|
302
|
+
if self.usextb != "None":
|
|
303
|
+
return tmp_name + "_NEB_" + self.usextb + "_" + timestamp + "/"
|
|
304
|
+
else:
|
|
305
|
+
return tmp_name + "_NEB_" + self.usedxtb + "_" + timestamp + "/"
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class CalculationEngineFactory:
|
|
309
|
+
"""Factory class for creating calculation engines"""
|
|
310
|
+
|
|
311
|
+
@staticmethod
|
|
312
|
+
def create_engine(config):
|
|
313
|
+
"""Create appropriate calculation engine based on configuration"""
|
|
314
|
+
if config.othersoft != "None":
|
|
315
|
+
if config.othersoft.lower() == "lj":
|
|
316
|
+
print("Use Lennard-Jones cluster potential.")
|
|
317
|
+
return LJEngine()
|
|
318
|
+
elif config.othersoft.lower() == "emt":
|
|
319
|
+
print("Use EMT cluster potential.")
|
|
320
|
+
return EMTEngine()
|
|
321
|
+
elif config.othersoft.lower() == "tersoff":
|
|
322
|
+
print("Use Tersoff cluster potential.")
|
|
323
|
+
return TersoffEngine()
|
|
324
|
+
else:
|
|
325
|
+
return ASEEngine(software_path_file=config.software_path_file)
|
|
326
|
+
elif config.sqm2:
|
|
327
|
+
return SQM2Engine()
|
|
328
|
+
elif config.sqm1:
|
|
329
|
+
return SQM1Engine()
|
|
330
|
+
elif config.usextb != "None":
|
|
331
|
+
return TBLiteEngine()
|
|
332
|
+
elif config.usedxtb != "None":
|
|
333
|
+
return DXTBEngine()
|
|
334
|
+
elif config.pyscf:
|
|
335
|
+
return PySCFEngine()
|
|
336
|
+
else:
|
|
337
|
+
return Psi4Engine()
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class OptimizationFactory:
|
|
341
|
+
"""Factory class for creating optimization algorithms"""
|
|
342
|
+
|
|
343
|
+
@staticmethod
|
|
344
|
+
def create_optimizer(method, config):
|
|
345
|
+
"""Create appropriate optimizer based on method and configuration"""
|
|
346
|
+
if method == "fire":
|
|
347
|
+
return FIREOptimizer(config)
|
|
348
|
+
elif method == "steepest_descent":
|
|
349
|
+
return SteepestDescentOptimizer(config)
|
|
350
|
+
elif method == "rfo" and (config.qsm or config.qsmv2):
|
|
351
|
+
return RFOQSMOptimizer(config)
|
|
352
|
+
elif method == "rfo":
|
|
353
|
+
tmp_opt = RFOOptimizer(config)
|
|
354
|
+
if config.not_ts_optimization:
|
|
355
|
+
print("Applying NEB without TS optimization.")
|
|
356
|
+
tmp_opt.set_apply_ts_opt(False)
|
|
357
|
+
return tmp_opt
|
|
358
|
+
elif method == "lbfgs":
|
|
359
|
+
tr_neb = trust_radius_neb.TR_NEB(
|
|
360
|
+
NEB_FOLDER_DIRECTORY=config.NEB_FOLDER_DIRECTORY,
|
|
361
|
+
fix_init_edge=config.fix_init_edge,
|
|
362
|
+
fix_end_edge=config.fix_end_edge,
|
|
363
|
+
apply_convergence_criteria=config.apply_convergence_criteria
|
|
364
|
+
)
|
|
365
|
+
return lbfgs_neb.LBFGS_NEB(TR_NEB=tr_neb)
|
|
366
|
+
elif method == "conjugate_gradient":
|
|
367
|
+
tr_neb = trust_radius_neb.TR_NEB(
|
|
368
|
+
NEB_FOLDER_DIRECTORY=config.NEB_FOLDER_DIRECTORY,
|
|
369
|
+
fix_init_edge=config.fix_init_edge,
|
|
370
|
+
fix_end_edge=config.fix_end_edge,
|
|
371
|
+
apply_convergence_criteria=config.apply_convergence_criteria
|
|
372
|
+
)
|
|
373
|
+
return conjugate_gradient_neb.ConjugateGradientNEB(TR_NEB=tr_neb, cg_method=config.cg_method)
|
|
374
|
+
else:
|
|
375
|
+
raise ValueError(f"Unsupported optimization method: {method}")
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
class NEB:
|
|
379
|
+
"""Main NEB (Nudged Elastic Band) calculation class (Refactored version)"""
|
|
380
|
+
|
|
381
|
+
def __init__(self, args):
|
|
382
|
+
# Store original args for backward compatibility
|
|
383
|
+
self.args = args
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def set_job(self, job):
|
|
387
|
+
self.args.JOB = job
|
|
388
|
+
|
|
389
|
+
def run(self):
|
|
390
|
+
if type(self.args.INPUT) is str:
|
|
391
|
+
START_FILE_LIST = [self.args.INPUT]
|
|
392
|
+
else:
|
|
393
|
+
START_FILE_LIST = self.args.INPUT #
|
|
394
|
+
|
|
395
|
+
job_file_list = []
|
|
396
|
+
|
|
397
|
+
for job_file in START_FILE_LIST:
|
|
398
|
+
print()
|
|
399
|
+
if "*" in job_file:
|
|
400
|
+
result_list = glob.glob(job_file)
|
|
401
|
+
job_file_list = job_file_list + result_list
|
|
402
|
+
else:
|
|
403
|
+
job_file_list = job_file_list + [job_file]
|
|
404
|
+
|
|
405
|
+
for job in job_file_list:
|
|
406
|
+
print("********************************")
|
|
407
|
+
print(job)
|
|
408
|
+
print("********************************")
|
|
409
|
+
if not os.path.exists(job):
|
|
410
|
+
print(f"{job} does not exist (neither as a file nor a directory).")
|
|
411
|
+
continue
|
|
412
|
+
self.set_job(job)
|
|
413
|
+
|
|
414
|
+
# Initialize configuration
|
|
415
|
+
self.config = NEBConfig(self.args)
|
|
416
|
+
|
|
417
|
+
# Create calculation engine
|
|
418
|
+
self.calculation_engine = CalculationEngineFactory.create_engine(self.config)
|
|
419
|
+
|
|
420
|
+
# Initialize visualizer if needed
|
|
421
|
+
if self.config.save_pict:
|
|
422
|
+
self.visualizer = NEBVisualizer(self.config)
|
|
423
|
+
|
|
424
|
+
# Create working directory
|
|
425
|
+
os.mkdir(self.config.NEB_FOLDER_DIRECTORY)
|
|
426
|
+
|
|
427
|
+
# Set element list (will be initialized in run method)
|
|
428
|
+
self.element_list = None
|
|
429
|
+
self.execute()
|
|
430
|
+
|
|
431
|
+
def execute(self):
|
|
432
|
+
"""Execute NEB calculation"""
|
|
433
|
+
# Load and prepare geometries
|
|
434
|
+
geometry_list, element_list, electric_charge_and_multiplicity = self.make_geometry_list(
|
|
435
|
+
self.config.init_input, self.config.partition)
|
|
436
|
+
self.element_list = element_list
|
|
437
|
+
self.config.element_list = element_list # Add to config for optimizer access
|
|
438
|
+
|
|
439
|
+
# Create initial input files
|
|
440
|
+
file_directory = self.make_input_files(geometry_list, 0)
|
|
441
|
+
|
|
442
|
+
# Initialize calculation variables
|
|
443
|
+
force_data = force_data_parser(self.args)
|
|
444
|
+
|
|
445
|
+
# Check for projection constraints
|
|
446
|
+
if len(force_data["projection_constraint_condition_list"]) > 0:
|
|
447
|
+
projection_constraint_flag = True
|
|
448
|
+
else:
|
|
449
|
+
projection_constraint_flag = False
|
|
450
|
+
|
|
451
|
+
# Get element number list
|
|
452
|
+
element_number_list = []
|
|
453
|
+
for elem in element_list:
|
|
454
|
+
element_number_list.append(element_number(elem))
|
|
455
|
+
element_number_list = np.array(element_number_list, dtype="int")
|
|
456
|
+
|
|
457
|
+
# Save input configuration
|
|
458
|
+
with open(self.config.NEB_FOLDER_DIRECTORY + "input.txt", "w") as f:
|
|
459
|
+
f.write(str(vars(self.args)))
|
|
460
|
+
|
|
461
|
+
# Setup force calculation method
|
|
462
|
+
STRING_FORCE_CALC = self._setup_force_calculation()
|
|
463
|
+
|
|
464
|
+
# Check for fixed atoms
|
|
465
|
+
if len(force_data["fix_atoms"]) > 0:
|
|
466
|
+
fix_atom_flag = True
|
|
467
|
+
else:
|
|
468
|
+
fix_atom_flag = False
|
|
469
|
+
|
|
470
|
+
# Initialize optimization variables
|
|
471
|
+
pre_geom = None
|
|
472
|
+
pre_total_force = None
|
|
473
|
+
pre_biased_gradient_list = None
|
|
474
|
+
pre_total_velocity = []
|
|
475
|
+
total_velocity = []
|
|
476
|
+
pre_biased_energy_list = None
|
|
477
|
+
|
|
478
|
+
# Check for conflicting optimization methods
|
|
479
|
+
if self.config.cg_method and self.config.lbfgs_method:
|
|
480
|
+
print("You can not use CG and LBFGS at the same time.")
|
|
481
|
+
exit()
|
|
482
|
+
|
|
483
|
+
# Setup optimizer
|
|
484
|
+
optimizer = self._setup_optimizer()
|
|
485
|
+
adaptive_neb_count = 0
|
|
486
|
+
# Main NEB iteration loop
|
|
487
|
+
for optimize_num in range(self.config.NEB_NUM):
|
|
488
|
+
exit_file_detect = os.path.exists(self.config.NEB_FOLDER_DIRECTORY + "end.txt")
|
|
489
|
+
if exit_file_detect:
|
|
490
|
+
if psi4:
|
|
491
|
+
psi4.core.clean()
|
|
492
|
+
break
|
|
493
|
+
|
|
494
|
+
print(f"\n Path Relaxation Method: ITR. {optimize_num} \n")
|
|
495
|
+
self.make_traj_file(file_directory)
|
|
496
|
+
|
|
497
|
+
# Calculate energy and gradients
|
|
498
|
+
energy_list, gradient_list, geometry_num_list, pre_total_velocity = \
|
|
499
|
+
self.calculation_engine.calculate(file_directory, adaptive_neb_count,
|
|
500
|
+
pre_total_velocity, self.config)
|
|
501
|
+
|
|
502
|
+
if adaptive_neb_count == 0:
|
|
503
|
+
init_geometry_num_list = geometry_num_list
|
|
504
|
+
|
|
505
|
+
# Apply bias potential - FIXED: Check if hessian files exist before using them
|
|
506
|
+
biased_energy_list, biased_gradient_list = self._apply_bias_potential(
|
|
507
|
+
energy_list, gradient_list, geometry_num_list, element_list, force_data, optimize_num)
|
|
508
|
+
|
|
509
|
+
# Initialize pre_biased_energy_list on first iteration
|
|
510
|
+
if adaptive_neb_count == 0:
|
|
511
|
+
pre_biased_energy_list = copy.copy(biased_energy_list)
|
|
512
|
+
|
|
513
|
+
# Calculate model hessian if needed
|
|
514
|
+
if (self.config.FC_COUNT == -1 and self.config.model_hessian and
|
|
515
|
+
adaptive_neb_count % self.config.MFC_COUNT == 0):
|
|
516
|
+
for i in range(len(geometry_num_list)):
|
|
517
|
+
hessian = ApproxHessian().main(geometry_num_list[i], element_list,
|
|
518
|
+
gradient_list[i], approx_hess_type=self.config.model_hessian)
|
|
519
|
+
np.save(self.config.NEB_FOLDER_DIRECTORY + "tmp_hessian_" + str(i) + ".npy", hessian)
|
|
520
|
+
|
|
521
|
+
# Initialize projection constraints
|
|
522
|
+
if projection_constraint_flag and adaptive_neb_count == 0:
|
|
523
|
+
PC_list = []
|
|
524
|
+
for i in range(len(energy_list)):
|
|
525
|
+
PC_list.append(ProjectOutConstrain(
|
|
526
|
+
force_data["projection_constraint_condition_list"],
|
|
527
|
+
force_data["projection_constraint_atoms"],
|
|
528
|
+
force_data["projection_constraint_constant"]))
|
|
529
|
+
PC_list[i].initialize(geometry_num_list[i])
|
|
530
|
+
|
|
531
|
+
# Apply projection constraints
|
|
532
|
+
if projection_constraint_flag:
|
|
533
|
+
for i in range(len(energy_list)):
|
|
534
|
+
biased_gradient_list[i] = copy.copy(PC_list[i].calc_project_out_grad(
|
|
535
|
+
geometry_num_list[i], biased_gradient_list[i]))
|
|
536
|
+
|
|
537
|
+
# Calculate forces
|
|
538
|
+
total_force = STRING_FORCE_CALC.calc_force(
|
|
539
|
+
geometry_num_list, biased_energy_list, biased_gradient_list, adaptive_neb_count, element_list)
|
|
540
|
+
|
|
541
|
+
# Calculate analysis metrics
|
|
542
|
+
cos_list, tot_force_rms_list, tot_force_max_list, bias_force_rms_list, path_length_list = \
|
|
543
|
+
self._calculate_analysis_metrics(total_force, biased_gradient_list, geometry_num_list)
|
|
544
|
+
|
|
545
|
+
# Save analysis data and create plots
|
|
546
|
+
self._save_analysis_data(cos_list, tot_force_rms_list, tot_force_max_list,
|
|
547
|
+
bias_force_rms_list, file_directory, optimize_num, path_length_list, biased_energy_list)
|
|
548
|
+
|
|
549
|
+
total_velocity = self.force2velocity(total_force, element_list)
|
|
550
|
+
|
|
551
|
+
# Perform optimization step
|
|
552
|
+
new_geometry = self._perform_optimization_step(
|
|
553
|
+
optimizer, geometry_num_list, total_force, biased_gradient_list,
|
|
554
|
+
pre_geom, pre_biased_gradient_list, adaptive_neb_count, biased_energy_list,
|
|
555
|
+
pre_biased_energy_list, pre_total_velocity, total_velocity,
|
|
556
|
+
cos_list, pre_geom, STRING_FORCE_CALC)
|
|
557
|
+
|
|
558
|
+
# Apply climbing image if needed
|
|
559
|
+
if (optimize_num > self.config.climbing_image_start and
|
|
560
|
+
(optimize_num - self.config.climbing_image_start) % self.config.climbing_image_interval == 0):
|
|
561
|
+
new_geometry = apply_climbing_image(new_geometry, biased_energy_list, element_list)
|
|
562
|
+
|
|
563
|
+
# Apply constraints
|
|
564
|
+
new_geometry = self._apply_constraints(
|
|
565
|
+
new_geometry, fix_atom_flag, force_data, init_geometry_num_list,
|
|
566
|
+
projection_constraint_flag, PC_list if projection_constraint_flag else None)
|
|
567
|
+
|
|
568
|
+
# Align geometries if needed
|
|
569
|
+
new_geometry = self._align_geometries(new_geometry, optimize_num, biased_gradient_list, biased_energy_list)
|
|
570
|
+
|
|
571
|
+
# Save analysis files
|
|
572
|
+
tmp_instance_fileio = FileIO(file_directory + "/", "dummy.txt")
|
|
573
|
+
tmp_instance_fileio.argrelextrema_txt_save(biased_energy_list, "approx_TS_node", "max")
|
|
574
|
+
tmp_instance_fileio.argrelextrema_txt_save(biased_energy_list, "approx_EQ_node", "min")
|
|
575
|
+
tmp_instance_fileio.argrelextrema_txt_save(bias_force_rms_list, "local_min_bias_grad_node", "min")
|
|
576
|
+
|
|
577
|
+
# Prepare for next iteration
|
|
578
|
+
if adaptive_neb_count % self.config.aneb_frequency == 0 and adaptive_neb_count > 0 and self.config.aneb_flag:
|
|
579
|
+
pre_geom = None
|
|
580
|
+
pre_total_force = None
|
|
581
|
+
pre_biased_gradient_list = None
|
|
582
|
+
pre_total_velocity = []
|
|
583
|
+
total_velocity = []
|
|
584
|
+
pre_biased_energy_list = None
|
|
585
|
+
new_geometry = self._exec_adaptive_neb(new_geometry, biased_energy_list)
|
|
586
|
+
geometry_list = self.print_geometry_list(new_geometry, element_list, electric_charge_and_multiplicity)
|
|
587
|
+
file_directory = self.make_input_files(geometry_list, optimize_num + 1)
|
|
588
|
+
adaptive_neb_count = 0
|
|
589
|
+
|
|
590
|
+
else:
|
|
591
|
+
pre_geom = copy.copy(geometry_num_list)
|
|
592
|
+
geometry_list = self.print_geometry_list(new_geometry, element_list, electric_charge_and_multiplicity)
|
|
593
|
+
file_directory = self.make_input_files(geometry_list, optimize_num + 1)
|
|
594
|
+
pre_total_force = copy.copy(total_force)
|
|
595
|
+
pre_biased_gradient_list = copy.copy(biased_gradient_list)
|
|
596
|
+
pre_total_velocity = copy.copy(total_velocity)
|
|
597
|
+
pre_biased_energy_list = copy.copy(biased_energy_list)
|
|
598
|
+
adaptive_neb_count += 1
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
self.make_traj_file(file_directory)
|
|
602
|
+
|
|
603
|
+
self.get_result_file()
|
|
604
|
+
print("Complete...")
|
|
605
|
+
return
|
|
606
|
+
|
|
607
|
+
def _exec_adaptive_neb(self, new_geometry, energy_list):
|
|
608
|
+
"""Execute the adaptive NEB algorithm (private method)"""
|
|
609
|
+
# ref.: P. Maragakis, S. A. Andreev, Y. Brumer, D. R. Reichman, E. Kaxiras, J. Chem. Phys. 117, 4651 (2002)
|
|
610
|
+
ene_max_val_indices = argrelmax(energy_list)[0]
|
|
611
|
+
print("Using Adaptive NEB method...")
|
|
612
|
+
if len(ene_max_val_indices) == 0:
|
|
613
|
+
print("Maxima not found.")
|
|
614
|
+
return new_geometry
|
|
615
|
+
if self.config.aneb_interpolation_num < 1:
|
|
616
|
+
print("Interpolation number is 0.")
|
|
617
|
+
return new_geometry
|
|
618
|
+
|
|
619
|
+
adaptive_neb_applied_new_geometry = []
|
|
620
|
+
for i in range(len(new_geometry)):
|
|
621
|
+
|
|
622
|
+
if i in ene_max_val_indices:
|
|
623
|
+
delta_geom_minus = new_geometry[i] - new_geometry[i-1]
|
|
624
|
+
delta_geom_plus = new_geometry[i+1] - new_geometry[i]
|
|
625
|
+
|
|
626
|
+
for j in range(self.config.aneb_interpolation_num):
|
|
627
|
+
alpha = (j + 1) / (self.config.aneb_interpolation_num + 1)
|
|
628
|
+
new_point = new_geometry[i-1] + alpha * delta_geom_minus
|
|
629
|
+
adaptive_neb_applied_new_geometry.append(new_point)
|
|
630
|
+
|
|
631
|
+
adaptive_neb_applied_new_geometry.append(new_geometry[i])
|
|
632
|
+
|
|
633
|
+
for j in range(self.config.aneb_interpolation_num):
|
|
634
|
+
alpha = (j + 1) / (self.config.aneb_interpolation_num + 1)
|
|
635
|
+
new_point = new_geometry[i] + alpha * delta_geom_plus
|
|
636
|
+
adaptive_neb_applied_new_geometry.append(new_point)
|
|
637
|
+
|
|
638
|
+
else:
|
|
639
|
+
adaptive_neb_applied_new_geometry.append(new_geometry[i])
|
|
640
|
+
|
|
641
|
+
adaptive_neb_applied_new_geometry = np.array(adaptive_neb_applied_new_geometry, dtype="float64")
|
|
642
|
+
print("Interpolated nodes: ", ene_max_val_indices)
|
|
643
|
+
print("The number of interpolated nodes: ", len(ene_max_val_indices) * 2 * self.config.aneb_interpolation_num)
|
|
644
|
+
return adaptive_neb_applied_new_geometry
|
|
645
|
+
|
|
646
|
+
def _align_geometries(self, new_geometry, optimize_num, gradient_list=None, energy_list=None):
|
|
647
|
+
"""Align geometries if needed based on configuration (private method)."""
|
|
648
|
+
|
|
649
|
+
# Early exit if optimization hasn't started or is at step 0
|
|
650
|
+
if optimize_num <= 0:
|
|
651
|
+
return new_geometry
|
|
652
|
+
|
|
653
|
+
n_nodes = len(new_geometry)
|
|
654
|
+
|
|
655
|
+
# Helper function to update the geometry list in-place
|
|
656
|
+
def update_geometry_in_place(result_geometry):
|
|
657
|
+
for k in range(n_nodes):
|
|
658
|
+
new_geometry[k] = copy.copy(result_geometry[k])
|
|
659
|
+
|
|
660
|
+
# Define alignment strategies
|
|
661
|
+
# Format: (config_attribute_name, function_to_call, log_message, kwargs_generator_lambda)
|
|
662
|
+
alignment_strategies = [
|
|
663
|
+
(
|
|
664
|
+
'align_distances',
|
|
665
|
+
distribute_geometry,
|
|
666
|
+
"Aligning geometries...",
|
|
667
|
+
lambda: {}
|
|
668
|
+
),
|
|
669
|
+
(
|
|
670
|
+
'align_distances_energy',
|
|
671
|
+
distribute_geometry_by_energy,
|
|
672
|
+
"Aligning geometries (energy-weighted)...",
|
|
673
|
+
# Fetch smoothing parameter from config, default to 0.1 if not present
|
|
674
|
+
lambda: {
|
|
675
|
+
'energy_list': energy_list,
|
|
676
|
+
'gradient_list': gradient_list,
|
|
677
|
+
'smoothing': getattr(self.config, 'align_distances_energy_smoothing', 0.02)
|
|
678
|
+
}
|
|
679
|
+
),
|
|
680
|
+
(
|
|
681
|
+
'align_distances_bernstein',
|
|
682
|
+
bernstein_interpolation,
|
|
683
|
+
"Aligning geometries using Bernstein interpolation...",
|
|
684
|
+
lambda: {'n_points': n_nodes}
|
|
685
|
+
),
|
|
686
|
+
(
|
|
687
|
+
'align_distances_bernstein_energy',
|
|
688
|
+
distribute_geometry_by_energy_bernstein,
|
|
689
|
+
"Aligning geometries using energy-weighted Bernstein interpolation...",
|
|
690
|
+
# Fetch smoothing parameter from config, default to 0.1 if not present
|
|
691
|
+
lambda: {
|
|
692
|
+
'energy_list': energy_list,
|
|
693
|
+
'gradient_list': gradient_list,
|
|
694
|
+
'smoothing': getattr(self.config, 'align_distances_bernstein_energy_smoothing', 0.5)
|
|
695
|
+
}
|
|
696
|
+
),
|
|
697
|
+
(
|
|
698
|
+
'align_distances_spline',
|
|
699
|
+
distribute_geometry_spline,
|
|
700
|
+
"Aligning geometries using spline...",
|
|
701
|
+
lambda: {}
|
|
702
|
+
),
|
|
703
|
+
(
|
|
704
|
+
'align_distances_spline_ver2',
|
|
705
|
+
spline_interpolation,
|
|
706
|
+
"Aligning geometries using spline ver2...",
|
|
707
|
+
lambda: {'n_points': n_nodes}
|
|
708
|
+
),
|
|
709
|
+
(
|
|
710
|
+
'align_distances_savgol',
|
|
711
|
+
savitzky_golay_interpolation,
|
|
712
|
+
"Aligning geometries using Savitzky-Golay filter...",
|
|
713
|
+
lambda: {
|
|
714
|
+
'number_of_nodes': n_nodes,
|
|
715
|
+
'window_length': getattr(self.config, 'align_distances_savgol_window', None),
|
|
716
|
+
'polyorder': getattr(self.config, 'align_distances_savgol_poly', None)
|
|
717
|
+
}
|
|
718
|
+
),
|
|
719
|
+
(
|
|
720
|
+
'align_distances_geodesic',
|
|
721
|
+
distribute_geometry_geodesic,
|
|
722
|
+
"Aligning geometries using geodesic interpolation...",
|
|
723
|
+
lambda: {}
|
|
724
|
+
),
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
(
|
|
728
|
+
'align_distances_adaptive_energy',
|
|
729
|
+
adaptive_geometry_energy_interpolation,
|
|
730
|
+
"Aligning geometries (Adaptive Geometry + Energy)...",
|
|
731
|
+
lambda: {'energy_list': energy_list, 'gradient_list': gradient_list, 'angle_threshold_deg': 15.0}
|
|
732
|
+
),
|
|
733
|
+
(
|
|
734
|
+
'align_distances_energy_predicted',
|
|
735
|
+
distribute_geometry_by_predicted_energy,
|
|
736
|
+
"Aligning geometries using energy-weighted predicted interpolation...",
|
|
737
|
+
lambda: {'energy_list': energy_list, 'gradient_list': gradient_list}
|
|
738
|
+
),
|
|
739
|
+
|
|
740
|
+
]
|
|
741
|
+
|
|
742
|
+
# Iterate through strategies and execute if the interval matches
|
|
743
|
+
for config_attr, func, message, kwargs_gen in alignment_strategies:
|
|
744
|
+
# Get the interval from config, default to 0 if attribute is missing
|
|
745
|
+
interval = getattr(self.config, config_attr, 0)
|
|
746
|
+
|
|
747
|
+
if interval >= 1 and optimize_num % interval == 0:
|
|
748
|
+
print(message)
|
|
749
|
+
|
|
750
|
+
# Execute the function with the generated keyword arguments
|
|
751
|
+
# The first argument is always assumed to be the geometry array
|
|
752
|
+
tmp_new_geometry = func(np.array(new_geometry), **kwargs_gen())
|
|
753
|
+
|
|
754
|
+
# Update the result
|
|
755
|
+
update_geometry_in_place(tmp_new_geometry)
|
|
756
|
+
|
|
757
|
+
return new_geometry
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def _setup_force_calculation(self):
|
|
761
|
+
"""Setup force calculation method"""
|
|
762
|
+
if self.config.om:
|
|
763
|
+
return CaluculationOM(self.config.APPLY_CI_NEB)
|
|
764
|
+
elif self.config.lup:
|
|
765
|
+
return CaluculationLUP(self.config.APPLY_CI_NEB)
|
|
766
|
+
elif self.config.dneb:
|
|
767
|
+
return CaluculationDNEB(self.config.APPLY_CI_NEB)
|
|
768
|
+
elif self.config.nesb:
|
|
769
|
+
return CaluculationNESB(self.config.APPLY_CI_NEB)
|
|
770
|
+
elif self.config.bneb:
|
|
771
|
+
return CaluculationBNEB2(self.config.APPLY_CI_NEB)
|
|
772
|
+
elif self.config.bneb2:
|
|
773
|
+
return CaluculationBNEB3(self.config.APPLY_CI_NEB)
|
|
774
|
+
elif self.config.ewbneb:
|
|
775
|
+
return CaluculationEWBNEB(self.config.APPLY_CI_NEB)
|
|
776
|
+
elif self.config.qsm:
|
|
777
|
+
return CaluculationQSM(self.config.APPLY_CI_NEB)
|
|
778
|
+
elif self.config.qsmv2:
|
|
779
|
+
return CaluculationQSMv2(self.config.APPLY_CI_NEB)
|
|
780
|
+
elif self.config.dmf:
|
|
781
|
+
return CaluculationDMF(self.config.APPLY_CI_NEB)
|
|
782
|
+
else:
|
|
783
|
+
return CaluculationBNEB(self.config.APPLY_CI_NEB)
|
|
784
|
+
|
|
785
|
+
def _setup_optimizer(self):
|
|
786
|
+
"""Setup optimization algorithm"""
|
|
787
|
+
# Determine optimization method based on configuration
|
|
788
|
+
if self.config.FC_COUNT != -1 or (self.config.MFC_COUNT != -1 and self.config.model_hessian):
|
|
789
|
+
return OptimizationFactory.create_optimizer("rfo", self.config)
|
|
790
|
+
elif self.config.lbfgs_method:
|
|
791
|
+
return OptimizationFactory.create_optimizer("lbfgs", self.config)
|
|
792
|
+
elif self.config.cg_method:
|
|
793
|
+
return OptimizationFactory.create_optimizer("conjugate_gradient", self.config)
|
|
794
|
+
else:
|
|
795
|
+
return OptimizationFactory.create_optimizer("fire", self.config)
|
|
796
|
+
|
|
797
|
+
def _apply_bias_potential(self, energy_list, gradient_list, geometry_num_list, element_list, force_data, optimize_num):
|
|
798
|
+
"""Apply bias potential to energies and gradients - FIXED: Check hessian file existence"""
|
|
799
|
+
biased_energy_list = []
|
|
800
|
+
biased_gradient_list = []
|
|
801
|
+
|
|
802
|
+
for i in range(len(energy_list)):
|
|
803
|
+
_, B_e, B_g, B_hess = BiasPotentialCalculation(
|
|
804
|
+
self.config.NEB_FOLDER_DIRECTORY).main(
|
|
805
|
+
energy_list[i], gradient_list[i], geometry_num_list[i],
|
|
806
|
+
element_list, force_data)
|
|
807
|
+
|
|
808
|
+
# FIXED: Only load hessian files if they exist and are needed
|
|
809
|
+
if self.config.FC_COUNT > 0 or (self.config.MFC_COUNT > 0 and self.config.model_hessian):
|
|
810
|
+
hessian_file = self.config.NEB_FOLDER_DIRECTORY + "tmp_hessian_" + str(i) + ".npy"
|
|
811
|
+
if os.path.exists(hessian_file):
|
|
812
|
+
hess = np.load(hessian_file)
|
|
813
|
+
np.save(hessian_file, B_hess + hess)
|
|
814
|
+
else:
|
|
815
|
+
# If hessian file doesn't exist yet, just save the bias hessian
|
|
816
|
+
# This can happen on the first iteration when exact hessians haven't been calculated yet
|
|
817
|
+
if not np.allclose(B_hess, 0): # Only save if bias hessian is non-zero
|
|
818
|
+
np.save(hessian_file, B_hess)
|
|
819
|
+
|
|
820
|
+
biased_energy_list.append(B_e)
|
|
821
|
+
biased_gradient_list.append(B_g)
|
|
822
|
+
|
|
823
|
+
return np.array(biased_energy_list, dtype="float64"), np.array(biased_gradient_list, dtype="float64")
|
|
824
|
+
|
|
825
|
+
def _calculate_analysis_metrics(self, total_force, biased_gradient_list, geometry_num_list):
|
|
826
|
+
"""Calculate analysis metrics for monitoring convergence"""
|
|
827
|
+
cos_list = []
|
|
828
|
+
tot_force_rms_list = []
|
|
829
|
+
tot_force_max_list = []
|
|
830
|
+
bias_force_rms_list = []
|
|
831
|
+
|
|
832
|
+
for i in range(len(total_force)):
|
|
833
|
+
# Calculate cosine between total force and biased gradient
|
|
834
|
+
total_force_norm = np.linalg.norm(total_force[i])
|
|
835
|
+
biased_grad_norm = np.linalg.norm(biased_gradient_list[i])
|
|
836
|
+
|
|
837
|
+
if total_force_norm > 1e-10 and biased_grad_norm > 1e-10:
|
|
838
|
+
cos = np.sum(total_force[i] * biased_gradient_list[i]) / (total_force_norm * biased_grad_norm)
|
|
839
|
+
else:
|
|
840
|
+
cos = 0.0
|
|
841
|
+
cos_list.append(cos)
|
|
842
|
+
|
|
843
|
+
tot_force_rms = np.sqrt(np.mean(total_force[i]**2))
|
|
844
|
+
tot_force_rms_list.append(tot_force_rms)
|
|
845
|
+
|
|
846
|
+
tot_force_max = np.max(np.abs(total_force[i]))
|
|
847
|
+
tot_force_max_list.append(tot_force_max)
|
|
848
|
+
|
|
849
|
+
bias_force_rms = np.sqrt(np.mean(biased_gradient_list[i]**2))
|
|
850
|
+
bias_force_rms_list.append(bias_force_rms)
|
|
851
|
+
|
|
852
|
+
path_length_list = calc_path_length_list(geometry_num_list)
|
|
853
|
+
|
|
854
|
+
return cos_list, tot_force_rms_list, tot_force_max_list, bias_force_rms_list, path_length_list
|
|
855
|
+
|
|
856
|
+
def _save_analysis_data(self, cos_list, tot_force_rms_list, tot_force_max_list,
|
|
857
|
+
bias_force_rms_list, file_directory, optimize_num, path_length_list, biased_energy_list):
|
|
858
|
+
"""Save analysis data and create plots"""
|
|
859
|
+
# Save path length data
|
|
860
|
+
with open(self.config.NEB_FOLDER_DIRECTORY + "path_length.csv", "a") as f:
|
|
861
|
+
f.write(",".join(list(map(str, path_length_list))) + "\n")
|
|
862
|
+
|
|
863
|
+
# Create energy vs path length plot
|
|
864
|
+
if self.config.save_pict:
|
|
865
|
+
self.visualizer.simple_scatter_plot(path_length_list, biased_energy_list, "", optimize_num, "Path length (ang.)", "Energy (Hartree)", "BE_PL")
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
# Save bias force RMS data
|
|
869
|
+
with open(self.config.NEB_FOLDER_DIRECTORY + "bias_force_rms.csv", "a") as f:
|
|
870
|
+
f.write(",".join(list(map(str, bias_force_rms_list))) + "\n")
|
|
871
|
+
|
|
872
|
+
# Create bias force RMS vs path length plot
|
|
873
|
+
if self.config.save_pict:
|
|
874
|
+
self.visualizer.simple_scatter_plot(path_length_list, bias_force_rms_list, "", optimize_num, "Path length (ang.)", "Bias force RMS (Hartree)", "BFRMS_PL")
|
|
875
|
+
|
|
876
|
+
# Create orthogonality plot
|
|
877
|
+
if self.config.save_pict:
|
|
878
|
+
self.visualizer.plot_orthogonality([x for x in range(len(cos_list))], cos_list, optimize_num)
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
# Save orthogonality data
|
|
882
|
+
with open(self.config.NEB_FOLDER_DIRECTORY + "orthogonality.csv", "a") as f:
|
|
883
|
+
f.write(",".join(list(map(str, cos_list))) + "\n")
|
|
884
|
+
|
|
885
|
+
# Create perpendicular gradient RMS plot
|
|
886
|
+
if self.config.save_pict:
|
|
887
|
+
self.visualizer.plot_perpendicular_gradient(
|
|
888
|
+
[x for x in range(len(tot_force_rms_list))][1:-1],
|
|
889
|
+
tot_force_rms_list[1:-1], optimize_num, "rms")
|
|
890
|
+
|
|
891
|
+
# Save perpendicular gradient RMS data
|
|
892
|
+
with open(self.config.NEB_FOLDER_DIRECTORY + "perp_rms_gradient.csv", "a") as f:
|
|
893
|
+
f.write(",".join(list(map(str, tot_force_rms_list))) + "\n")
|
|
894
|
+
|
|
895
|
+
# Create perpendicular gradient MAX plot
|
|
896
|
+
if self.config.save_pict:
|
|
897
|
+
self.visualizer.plot_perpendicular_gradient(
|
|
898
|
+
[x for x in range(len(tot_force_max_list))],
|
|
899
|
+
tot_force_max_list, optimize_num, "max")
|
|
900
|
+
|
|
901
|
+
# Save perpendicular gradient MAX data
|
|
902
|
+
with open(self.config.NEB_FOLDER_DIRECTORY + "perp_max_gradient.csv", "a") as f:
|
|
903
|
+
f.write(",".join(list(map(str, tot_force_max_list))) + "\n")
|
|
904
|
+
|
|
905
|
+
# Save energy data
|
|
906
|
+
with open(self.config.NEB_FOLDER_DIRECTORY + "energy_plot.csv", "a") as f:
|
|
907
|
+
f.write(",".join(list(map(str, biased_energy_list.tolist()))) + "\n")
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
def _perform_optimization_step(self, optimizer, geometry_num_list, total_force,
|
|
912
|
+
biased_gradient_list, pre_geom, pre_biased_gradient_list,
|
|
913
|
+
optimize_num, biased_energy_list, pre_biased_energy_list,
|
|
914
|
+
pre_total_velocity, total_velocity, cos_list,
|
|
915
|
+
pre_geom_param, STRING_FORCE_CALC):
|
|
916
|
+
"""Perform optimization step based on the selected method"""
|
|
917
|
+
if isinstance(optimizer, RFOOptimizer):
|
|
918
|
+
# RFO optimization
|
|
919
|
+
return optimizer.optimize(
|
|
920
|
+
geometry_num_list, biased_gradient_list, pre_geom, pre_biased_gradient_list,
|
|
921
|
+
optimize_num, biased_energy_list, pre_biased_energy_list,
|
|
922
|
+
pre_total_velocity, total_velocity, cos_list, pre_geom_param, STRING_FORCE_CALC)
|
|
923
|
+
elif isinstance(optimizer, RFOQSMOptimizer):
|
|
924
|
+
# RFOQSM optimization
|
|
925
|
+
return optimizer.optimize(
|
|
926
|
+
geometry_num_list, biased_gradient_list, pre_geom, pre_biased_gradient_list,
|
|
927
|
+
optimize_num, biased_energy_list, pre_biased_energy_list,
|
|
928
|
+
pre_total_velocity, total_velocity, cos_list, pre_geom_param, STRING_FORCE_CALC)
|
|
929
|
+
elif isinstance(optimizer, FIREOptimizer):
|
|
930
|
+
# FIRE optimization
|
|
931
|
+
if optimize_num < self.config.sd:
|
|
932
|
+
return optimizer.optimize(
|
|
933
|
+
geometry_num_list, total_force, pre_total_velocity, optimize_num,
|
|
934
|
+
total_velocity, cos_list, biased_energy_list, pre_biased_energy_list, pre_geom_param)
|
|
935
|
+
else:
|
|
936
|
+
# Switch to steepest descent
|
|
937
|
+
sd_optimizer = SteepestDescentOptimizer(self.config)
|
|
938
|
+
return sd_optimizer.optimize(geometry_num_list, total_force)
|
|
939
|
+
elif isinstance(optimizer, SteepestDescentOptimizer):
|
|
940
|
+
return optimizer.optimize(geometry_num_list, total_force)
|
|
941
|
+
elif hasattr(optimizer, 'LBFGS_NEB_calc'):
|
|
942
|
+
# LBFGS optimization
|
|
943
|
+
return optimizer.LBFGS_NEB_calc(
|
|
944
|
+
geometry_num_list, total_force, pre_total_velocity, optimize_num,
|
|
945
|
+
total_velocity, cos_list, biased_energy_list, pre_biased_energy_list, pre_geom_param)
|
|
946
|
+
elif hasattr(optimizer, 'CG_NEB_calc'):
|
|
947
|
+
# Conjugate gradient optimization
|
|
948
|
+
return optimizer.CG_NEB_calc(
|
|
949
|
+
geometry_num_list, total_force, pre_total_velocity, optimize_num,
|
|
950
|
+
total_velocity, cos_list, biased_energy_list, pre_biased_energy_list, pre_geom_param)
|
|
951
|
+
else:
|
|
952
|
+
# Default to FIRE
|
|
953
|
+
fire_optimizer = FIREOptimizer(self.config)
|
|
954
|
+
return fire_optimizer.optimize(
|
|
955
|
+
geometry_num_list, total_force, pre_total_velocity, optimize_num,
|
|
956
|
+
total_velocity, cos_list, biased_energy_list, pre_biased_energy_list, pre_geom_param)
|
|
957
|
+
|
|
958
|
+
def _apply_constraints(self, new_geometry, fix_atom_flag, force_data,
|
|
959
|
+
init_geometry_num_list, projection_constraint_flag, PC_list):
|
|
960
|
+
"""Apply various constraints to the new geometry"""
|
|
961
|
+
|
|
962
|
+
# Apply fixing edge node
|
|
963
|
+
if self.config.fix_init_edge:
|
|
964
|
+
new_geometry[0] = init_geometry_num_list[0] * self.config.bohr2angstroms
|
|
965
|
+
if self.config.fix_end_edge:
|
|
966
|
+
new_geometry[-1] = init_geometry_num_list[-1] * self.config.bohr2angstroms
|
|
967
|
+
|
|
968
|
+
|
|
969
|
+
# Apply fixed atoms constraint
|
|
970
|
+
if fix_atom_flag:
|
|
971
|
+
for k in range(len(new_geometry)):
|
|
972
|
+
for j in force_data["fix_atoms"]:
|
|
973
|
+
new_geometry[k][j-1] = copy.copy(init_geometry_num_list[k][j-1] * self.config.bohr2angstroms)
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
# Apply projection constraints
|
|
977
|
+
if projection_constraint_flag:
|
|
978
|
+
for x in range(len(new_geometry)):
|
|
979
|
+
tmp_new_geometry = new_geometry[x] / self.config.bohr2angstroms
|
|
980
|
+
tmp_new_geometry = PC_list[x].adjust_init_coord(tmp_new_geometry) * self.config.bohr2angstroms
|
|
981
|
+
new_geometry[x] = copy.copy(tmp_new_geometry)
|
|
982
|
+
|
|
983
|
+
# Apply Kabsch alignment if no fixed atoms
|
|
984
|
+
if not fix_atom_flag:
|
|
985
|
+
for k in range(len(new_geometry)-1):
|
|
986
|
+
tmp_new_geometry, _ = Calculationtools().kabsch_algorithm(new_geometry[k], new_geometry[k+1])
|
|
987
|
+
new_geometry[k] = copy.copy(tmp_new_geometry)
|
|
988
|
+
|
|
989
|
+
return new_geometry
|
|
990
|
+
|
|
991
|
+
def make_geometry_list(self, init_input, partition_function):
|
|
992
|
+
"""Create geometry list from input files"""
|
|
993
|
+
if os.path.splitext(init_input)[1] == ".xyz":
|
|
994
|
+
self.config.init_input = os.path.splitext(init_input)[0]
|
|
995
|
+
xyz_flag = True
|
|
996
|
+
else:
|
|
997
|
+
xyz_flag = False
|
|
998
|
+
|
|
999
|
+
start_file_list = sum([sorted(glob.glob(os.path.join(init_input, f"*_" + "[0-9]" * i + ".xyz")))
|
|
1000
|
+
for i in range(1, 7)], [])
|
|
1001
|
+
|
|
1002
|
+
loaded_geometry_list = []
|
|
1003
|
+
|
|
1004
|
+
if xyz_flag:
|
|
1005
|
+
geometry_list, elements, electric_charge_and_multiplicity = traj2list(
|
|
1006
|
+
init_input, [self.config.electronic_charge, self.config.spin_multiplicity])
|
|
1007
|
+
|
|
1008
|
+
element_list = elements[0]
|
|
1009
|
+
|
|
1010
|
+
for i in range(len(geometry_list)):
|
|
1011
|
+
loaded_geometry_list.append([electric_charge_and_multiplicity] +
|
|
1012
|
+
[[element_list[num]] + list(map(str, geometry))
|
|
1013
|
+
for num, geometry in enumerate(geometry_list[i])])
|
|
1014
|
+
else:
|
|
1015
|
+
for start_file in start_file_list:
|
|
1016
|
+
tmp_geometry_list, element_list, electric_charge_and_multiplicity = xyz2list(
|
|
1017
|
+
start_file, [self.config.electronic_charge, self.config.spin_multiplicity])
|
|
1018
|
+
tmp_data = [electric_charge_and_multiplicity]
|
|
1019
|
+
|
|
1020
|
+
for i in range(len(tmp_geometry_list)):
|
|
1021
|
+
tmp_data.append([element_list[i]] + list(map(str, tmp_geometry_list[i])))
|
|
1022
|
+
loaded_geometry_list.append(tmp_data)
|
|
1023
|
+
|
|
1024
|
+
electric_charge_and_multiplicity = loaded_geometry_list[0][0]
|
|
1025
|
+
element_list = [row[0] for row in loaded_geometry_list[0][1:]]
|
|
1026
|
+
|
|
1027
|
+
loaded_geometry_num_list = [[list(map(float, row[1:4])) for row in geometry[1:]]
|
|
1028
|
+
for geometry in loaded_geometry_list]
|
|
1029
|
+
|
|
1030
|
+
geometry_list = [loaded_geometry_list[0]]
|
|
1031
|
+
|
|
1032
|
+
tmp_data = []
|
|
1033
|
+
|
|
1034
|
+
for k in range(len(loaded_geometry_list) - 1):
|
|
1035
|
+
delta_num_geom = (np.array(loaded_geometry_num_list[k + 1], dtype="float64") -
|
|
1036
|
+
np.array(loaded_geometry_num_list[k], dtype="float64")) / (partition_function + 1)
|
|
1037
|
+
|
|
1038
|
+
for i in range(partition_function + 1):
|
|
1039
|
+
frame_geom = np.array(loaded_geometry_num_list[k], dtype="float64") + delta_num_geom * i
|
|
1040
|
+
tmp_data.append(frame_geom)
|
|
1041
|
+
tmp_data.append(np.array(loaded_geometry_num_list[-1], dtype="float64"))
|
|
1042
|
+
tmp_data = np.array(tmp_data, dtype="float64")
|
|
1043
|
+
|
|
1044
|
+
# Apply IDPP if requested
|
|
1045
|
+
if self.config.IDPP_flag:
|
|
1046
|
+
IDPP_obj = IDPP()
|
|
1047
|
+
tmp_data = IDPP_obj.opt_path(tmp_data, element_list)
|
|
1048
|
+
|
|
1049
|
+
# Apply CFB_ENM if requested
|
|
1050
|
+
if self.config.CFB_ENM_flag:
|
|
1051
|
+
CFB_ENM_obj = CFB_ENM()
|
|
1052
|
+
tmp_data = CFB_ENM_obj.opt_path(tmp_data, element_list)
|
|
1053
|
+
|
|
1054
|
+
# Align distances if requested
|
|
1055
|
+
if self.config.align_distances > 0:
|
|
1056
|
+
tmp_data = distribute_geometry(tmp_data)
|
|
1057
|
+
|
|
1058
|
+
if self.config.align_distances_bernstein > 0:
|
|
1059
|
+
tmp_data = bernstein_interpolation(tmp_data, len(tmp_data))
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
if self.config.align_distances_spline_ver2 > 0:
|
|
1063
|
+
tmp_data = spline_interpolation(tmp_data, len(tmp_data))
|
|
1064
|
+
|
|
1065
|
+
if self.config.align_distances_savgol > 0:
|
|
1066
|
+
tmp_data = savitzky_golay_interpolation(tmp_data, len(tmp_data), window_length=self.config.align_distances_savgol_window, polyorder=self.config.align_distances_savgol_poly)
|
|
1067
|
+
|
|
1068
|
+
if self.config.align_distances_geodesic > 0:
|
|
1069
|
+
tmp_data = distribute_geometry_geodesic(tmp_data)
|
|
1070
|
+
|
|
1071
|
+
# Apply node distance constraint if specified
|
|
1072
|
+
if self.config.node_distance is not None:
|
|
1073
|
+
tmp_data = distribute_geometry_by_length(tmp_data, self.config.node_distance)
|
|
1074
|
+
|
|
1075
|
+
if self.config.node_distance_spline is not None:
|
|
1076
|
+
tmp_data = distribute_geometry_by_length_spline(tmp_data, self.config.node_distance_spline)
|
|
1077
|
+
|
|
1078
|
+
if self.config.node_distance_bernstein is not None:
|
|
1079
|
+
tmp_data = distribute_geometry_by_length_bernstein(tmp_data, self.config.node_distance_bernstein)
|
|
1080
|
+
|
|
1081
|
+
if self.config.node_distance_savgol is not None:
|
|
1082
|
+
tmp_data = distribute_geometry_by_length_savgol(tmp_data, self.config.node_distance_savgol, window_length=self.config.node_distance_savgol_window, polyorder=self.config.node_distance_savgol_poly)
|
|
1083
|
+
|
|
1084
|
+
for data in tmp_data:
|
|
1085
|
+
geometry_list.append([electric_charge_and_multiplicity] +
|
|
1086
|
+
[[element_list[num]] + list(map(str, geometry))
|
|
1087
|
+
for num, geometry in enumerate(data)])
|
|
1088
|
+
|
|
1089
|
+
print("\n Geometries are loaded. \n")
|
|
1090
|
+
return geometry_list, element_list, electric_charge_and_multiplicity
|
|
1091
|
+
|
|
1092
|
+
def print_geometry_list(self, new_geometry, element_list, electric_charge_and_multiplicity):
|
|
1093
|
+
"""Convert geometry array back to list format"""
|
|
1094
|
+
new_geometry = new_geometry.tolist()
|
|
1095
|
+
geometry_list = []
|
|
1096
|
+
for geometries in new_geometry:
|
|
1097
|
+
new_data = [electric_charge_and_multiplicity]
|
|
1098
|
+
for num, geometry in enumerate(geometries):
|
|
1099
|
+
geometry_data = list(map(str, geometry))
|
|
1100
|
+
geometry_data = [element_list[num]] + geometry_data
|
|
1101
|
+
new_data.append(geometry_data)
|
|
1102
|
+
|
|
1103
|
+
geometry_list.append(new_data)
|
|
1104
|
+
return geometry_list
|
|
1105
|
+
|
|
1106
|
+
def make_input_files(self, geometry_list, optimize_num):
|
|
1107
|
+
"""Create input files for calculations"""
|
|
1108
|
+
file_directory = self.config.NEB_FOLDER_DIRECTORY + "path_ITR_" + str(optimize_num) + "_" + str(self.config.init_input)
|
|
1109
|
+
try:
|
|
1110
|
+
os.mkdir(file_directory)
|
|
1111
|
+
except:
|
|
1112
|
+
pass
|
|
1113
|
+
tmp_cs = [self.config.electronic_charge, self.config.spin_multiplicity]
|
|
1114
|
+
float_pattern = r"([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?)"
|
|
1115
|
+
|
|
1116
|
+
for y, geometry in enumerate(geometry_list):
|
|
1117
|
+
tmp_geometry = []
|
|
1118
|
+
for geom in geometry:
|
|
1119
|
+
if len(geom) == 4 and re.match(r"[A-Za-z]+", str(geom[0])) \
|
|
1120
|
+
and all(re.match(float_pattern, str(x)) for x in geom[1:]):
|
|
1121
|
+
tmp_geometry.append(geom)
|
|
1122
|
+
|
|
1123
|
+
if len(geom) == 2 and re.match(r"-*\d+", str(geom[0])) and re.match(r"-*\d+", str(geom[1])):
|
|
1124
|
+
tmp_cs = geom
|
|
1125
|
+
|
|
1126
|
+
with open(file_directory + "/" + self.config.init_input + "_" + str(y) + ".xyz", "w") as w:
|
|
1127
|
+
w.write(str(len(tmp_geometry)) + "\n")
|
|
1128
|
+
w.write(str(tmp_cs[0]) + " " + str(tmp_cs[1]) + "\n")
|
|
1129
|
+
for rows in tmp_geometry:
|
|
1130
|
+
w.write(f"{rows[0]:2} {float(rows[1]):>17.12f} {float(rows[2]):>17.12f} {float(rows[3]):>17.12f}\n")
|
|
1131
|
+
return file_directory
|
|
1132
|
+
|
|
1133
|
+
def make_traj_file(self, file_directory):
|
|
1134
|
+
"""Create trajectory file from current geometries"""
|
|
1135
|
+
print("\nprocessing geometry collecting ...\n")
|
|
1136
|
+
file_list = sum([sorted(glob.glob(os.path.join(file_directory, f"*_" + "[0-9]" * i + ".xyz")))
|
|
1137
|
+
for i in range(1, 7)], [])
|
|
1138
|
+
|
|
1139
|
+
for m, file in enumerate(file_list):
|
|
1140
|
+
tmp_geometry_list, element_list, _ = xyz2list(file, None)
|
|
1141
|
+
atom_num = len(tmp_geometry_list)
|
|
1142
|
+
with open(file_directory + "/" + self.config.init_input + "_path.xyz", "a") as w:
|
|
1143
|
+
w.write(str(atom_num) + "\n")
|
|
1144
|
+
w.write("Frame " + str(m) + "\n")
|
|
1145
|
+
for i in range(len(tmp_geometry_list)):
|
|
1146
|
+
w.write(f"{element_list[i]:2} {float(tmp_geometry_list[i][0]):17.12f} {float(tmp_geometry_list[i][1]):17.12f} {float(tmp_geometry_list[i][2]):17.12f}\n")
|
|
1147
|
+
print("\ncollecting geometries was complete...\n")
|
|
1148
|
+
return
|
|
1149
|
+
|
|
1150
|
+
def force2velocity(self, gradient_list, element_list):
|
|
1151
|
+
"""Convert force to velocity"""
|
|
1152
|
+
velocity_list = gradient_list
|
|
1153
|
+
return np.array(velocity_list, dtype="float64")
|
|
1154
|
+
|
|
1155
|
+
def get_result_file(self):
|
|
1156
|
+
"""
|
|
1157
|
+
Gets the absolute file paths for the geometry files corresponding to
|
|
1158
|
+
energy maxima (TS candidates) from the final iteration results.
|
|
1159
|
+
"""
|
|
1160
|
+
self.ts_guess_file_list = []
|
|
1161
|
+
|
|
1162
|
+
# 1. Get the last energy list from energy_plot.csv
|
|
1163
|
+
energy_file_path = os.path.join(self.config.NEB_FOLDER_DIRECTORY, "energy_plot.csv")
|
|
1164
|
+
|
|
1165
|
+
if not os.path.exists(energy_file_path):
|
|
1166
|
+
print(f"Error: Energy log file not found: {energy_file_path}")
|
|
1167
|
+
return
|
|
1168
|
+
|
|
1169
|
+
try:
|
|
1170
|
+
with open(energy_file_path, 'r') as f:
|
|
1171
|
+
lines = f.readlines()
|
|
1172
|
+
if not lines:
|
|
1173
|
+
print(f"Error: {energy_file_path} is empty.")
|
|
1174
|
+
return
|
|
1175
|
+
# Get the last line
|
|
1176
|
+
last_line = lines[-1].strip()
|
|
1177
|
+
|
|
1178
|
+
# Load the energy list from CSV as a numpy array
|
|
1179
|
+
biased_energy_list = np.array([float(e) for e in last_line.split(',') if e.strip()])
|
|
1180
|
+
|
|
1181
|
+
except Exception as e:
|
|
1182
|
+
print(f"Error: Failed to read {energy_file_path}: {e}")
|
|
1183
|
+
return
|
|
1184
|
+
|
|
1185
|
+
if len(biased_energy_list) == 0:
|
|
1186
|
+
print("Error: No energy data found in the last line of energy_plot.csv.")
|
|
1187
|
+
return
|
|
1188
|
+
|
|
1189
|
+
# 2. Identify the indices (Z) of energy maxima
|
|
1190
|
+
ts_indices = np.array([], dtype=int)
|
|
1191
|
+
if len(biased_energy_list) > 2:
|
|
1192
|
+
# Find local maxima, excluding endpoints (0 and -1)
|
|
1193
|
+
ts_indices = argrelmax(biased_energy_list[1:-1])[0] + 1
|
|
1194
|
+
|
|
1195
|
+
# If no local maxima are found (e.g., monotonic path),
|
|
1196
|
+
# set the highest energy point (excluding endpoints) as the TS candidate.
|
|
1197
|
+
if len(ts_indices) == 0:
|
|
1198
|
+
print("No local maxima found. Setting the highest energy point (excluding endpoints) as the TS candidate.")
|
|
1199
|
+
ts_indices = np.array([np.argmax(biased_energy_list[1:-1]) + 1])
|
|
1200
|
+
|
|
1201
|
+
elif len(biased_energy_list) > 0:
|
|
1202
|
+
print("Warning: Path has 2 or fewer images. Cannot identify a non-endpoint TS.")
|
|
1203
|
+
|
|
1204
|
+
else:
|
|
1205
|
+
return # Energy list is empty (already caught above, but just in case)
|
|
1206
|
+
|
|
1207
|
+
#print(f"Final energy profile (kcal/mol, relative to start): {[(e - biased_energy_list[0]) * self.config.hartree2kcalmol for e in biased_energy_list]}")
|
|
1208
|
+
print(f"TS candidate indices: {ts_indices}")
|
|
1209
|
+
|
|
1210
|
+
if len(ts_indices) == 0:
|
|
1211
|
+
print("No TS candidates were found.")
|
|
1212
|
+
return
|
|
1213
|
+
|
|
1214
|
+
# 3. Identify the last iteration directory (path_ITR_YYY_XXX)
|
|
1215
|
+
# Find the directory with the largest YYY
|
|
1216
|
+
itr_dirs = glob.glob(os.path.join(self.config.NEB_FOLDER_DIRECTORY, "path_ITR_*"))
|
|
1217
|
+
if not itr_dirs:
|
|
1218
|
+
print(f"Error: Iteration directories not found in {self.config.NEB_FOLDER_DIRECTORY}.")
|
|
1219
|
+
return
|
|
1220
|
+
|
|
1221
|
+
last_itr_num = -1
|
|
1222
|
+
last_itr_dir = ""
|
|
1223
|
+
|
|
1224
|
+
# Pattern: path_ITR_(number)_(init_input_name)
|
|
1225
|
+
# Escape special characters in init_input with re.escape and confirm it matches the end ($)
|
|
1226
|
+
base_name = re.escape(self.config.init_input)
|
|
1227
|
+
pattern = re.compile(r"path_ITR_(\d+)_" + base_name + r"$")
|
|
1228
|
+
|
|
1229
|
+
for d in itr_dirs:
|
|
1230
|
+
dir_name = os.path.basename(d) # Get the directory name itself
|
|
1231
|
+
match = pattern.match(dir_name)
|
|
1232
|
+
if match:
|
|
1233
|
+
try:
|
|
1234
|
+
itr_num = int(match.group(1))
|
|
1235
|
+
if itr_num > last_itr_num:
|
|
1236
|
+
last_itr_num = itr_num
|
|
1237
|
+
last_itr_dir = d
|
|
1238
|
+
except ValueError:
|
|
1239
|
+
continue
|
|
1240
|
+
|
|
1241
|
+
if not last_itr_dir:
|
|
1242
|
+
print(f"Error: No valid last iteration directory ('path_ITR_*_{self.config.init_input}') found.")
|
|
1243
|
+
return
|
|
1244
|
+
|
|
1245
|
+
print(f"Last iteration directory: {last_itr_dir}")
|
|
1246
|
+
|
|
1247
|
+
# 4. Assemble the file paths and add to the list
|
|
1248
|
+
for z in ts_indices:
|
|
1249
|
+
# File format: XXX_Z.xyz (e.g., input_name_5.xyz)
|
|
1250
|
+
file_name = f"{self.config.init_input}_{z}.xyz"
|
|
1251
|
+
file_path = os.path.join(last_itr_dir, file_name)
|
|
1252
|
+
|
|
1253
|
+
if os.path.exists(file_path):
|
|
1254
|
+
# Get the absolute path, as requested
|
|
1255
|
+
abs_path = os.path.abspath(file_path)
|
|
1256
|
+
self.ts_guess_file_list.append(abs_path)
|
|
1257
|
+
print(f"Found TS candidate file: {abs_path}")
|
|
1258
|
+
else:
|
|
1259
|
+
print(f"Warning: Expected file not found: {file_path}")
|
|
1260
|
+
|
|
1261
|
+
# 5. Get traj file path
|
|
1262
|
+
last_itr_traj_file_path = os.path.join(last_itr_dir, f"{self.config.init_input}_path.xyz")
|
|
1263
|
+
self.last_itr_traj_file_path = last_itr_traj_file_path
|
|
1264
|
+
|
|
1265
|
+
return
|
|
1266
|
+
|
|
1267
|
+
|