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,1553 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import copy
|
|
4
|
+
import glob
|
|
5
|
+
import itertools
|
|
6
|
+
import datetime
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from multioptpy.optimizer import CalculateMoveVector
|
|
12
|
+
from multioptpy.Visualization.visualization import Graph
|
|
13
|
+
from multioptpy.fileio import FileIO
|
|
14
|
+
from multioptpy.Parameters.parameter import UnitValueLib, element_number
|
|
15
|
+
from multioptpy.interface import force_data_parser
|
|
16
|
+
from multioptpy.ModelHessian.approx_hessian import ApproxHessian
|
|
17
|
+
from multioptpy.PESAnalyzer.cmds_analysis import CMDSPathAnalysis
|
|
18
|
+
from multioptpy.PESAnalyzer.pca_analysis import PCAPathAnalysis
|
|
19
|
+
from multioptpy.PESAnalyzer.koopman_analysis import KoopmanAnalyzer
|
|
20
|
+
from multioptpy.Potential.potential import BiasPotentialCalculation
|
|
21
|
+
from multioptpy.Utils.calc_tools import CalculationStructInfo, Calculationtools
|
|
22
|
+
from multioptpy.Constraint.constraint_condition import ProjectOutConstrain
|
|
23
|
+
from multioptpy.irc import IRC
|
|
24
|
+
from multioptpy.Utils.bond_connectivity import judge_shape_condition
|
|
25
|
+
from multioptpy.Utils.oniom import separate_high_layer_and_low_layer, specify_link_atom_pairs, link_number_high_layer_and_low_layer
|
|
26
|
+
from multioptpy.Utils.symmetry_analyzer import analyze_symmetry
|
|
27
|
+
from multioptpy.Thermo.normal_mode_analyzer import MolecularVibrations
|
|
28
|
+
|
|
29
|
+
# Responsibility 1: Holds the "Configuration" (immutable)
|
|
30
|
+
class OptimizationConfig:
|
|
31
|
+
"""
|
|
32
|
+
Holds all "settings" that do not change during the run.
|
|
33
|
+
Initialized from 'args'.
|
|
34
|
+
"""
|
|
35
|
+
def __init__(self, args):
|
|
36
|
+
# Constants like UVL
|
|
37
|
+
UVL = UnitValueLib()
|
|
38
|
+
np.set_printoptions(precision=12, floatmode="fixed", suppress=True)
|
|
39
|
+
self.hartree2kcalmol = UVL.hartree2kcalmol
|
|
40
|
+
self.bohr2angstroms = UVL.bohr2angstroms
|
|
41
|
+
self.hartree2kjmol = UVL.hartree2kjmol
|
|
42
|
+
|
|
43
|
+
# Port the logic from _set_convergence_criteria
|
|
44
|
+
self._set_convergence_criteria(args)
|
|
45
|
+
|
|
46
|
+
# Port all "immutable" settings from _initialize_variables
|
|
47
|
+
self.microiter_num = 100
|
|
48
|
+
self.args = args # Keep a reference to args
|
|
49
|
+
self.FC_COUNT = args.calc_exact_hess
|
|
50
|
+
self.temperature = 0.0
|
|
51
|
+
self.CMDS = args.cmds
|
|
52
|
+
self.PCA = args.pca
|
|
53
|
+
self.DELTA = "x" if args.DELTA == "x" else float(args.DELTA)
|
|
54
|
+
self.N_THREAD = args.N_THREAD
|
|
55
|
+
self.SET_MEMORY = args.SET_MEMORY
|
|
56
|
+
self.NSTEP = args.NSTEP
|
|
57
|
+
self.BASIS_SET = args.basisset
|
|
58
|
+
self.FUNCTIONAL = args.functional
|
|
59
|
+
self.excited_state = args.excited_state
|
|
60
|
+
|
|
61
|
+
# Port the logic from _check_sub_basisset
|
|
62
|
+
self._check_sub_basisset(args)
|
|
63
|
+
|
|
64
|
+
self.mFC_COUNT = args.calc_model_hess
|
|
65
|
+
self.DC_check_dist = float(args.dissociate_check)
|
|
66
|
+
self.unrestrict = args.unrestrict
|
|
67
|
+
self.irc = args.intrinsic_reaction_coordinates
|
|
68
|
+
self.othersoft = args.othersoft
|
|
69
|
+
self.cpcm_solv_model = args.cpcm_solv_model
|
|
70
|
+
self.alpb_solv_model = args.alpb_solv_model
|
|
71
|
+
self.shape_conditions = args.shape_conditions
|
|
72
|
+
self.oniom = args.oniom_flag
|
|
73
|
+
self.use_model_hessian = args.use_model_hessian
|
|
74
|
+
self.sqm1 = args.sqm1
|
|
75
|
+
self.sqm2 = args.sqm2
|
|
76
|
+
self.freq_analysis = args.frequency_analysis
|
|
77
|
+
self.thermo_temperature = float(args.temperature)
|
|
78
|
+
self.thermo_pressure = float(args.pressure)
|
|
79
|
+
self.dft_grid = int(args.dft_grid)
|
|
80
|
+
self.max_trust_radius = args.max_trust_radius
|
|
81
|
+
self.min_trust_radius = args.min_trust_radius
|
|
82
|
+
self.software_path_file = args.software_path_file
|
|
83
|
+
self.koopman_analysis = args.koopman
|
|
84
|
+
self.detect_negative_eigenvalues = args.detect_negative_eigenvalues
|
|
85
|
+
|
|
86
|
+
def _set_convergence_criteria(self, args):
|
|
87
|
+
# Original _set_convergence_criteria method code
|
|
88
|
+
if args.tight_convergence_criteria and not args.loose_convergence_criteria:
|
|
89
|
+
self.MAX_FORCE_THRESHOLD = 0.000015
|
|
90
|
+
self.RMS_FORCE_THRESHOLD = 0.000010
|
|
91
|
+
self.MAX_DISPLACEMENT_THRESHOLD = 0.000060
|
|
92
|
+
self.RMS_DISPLACEMENT_THRESHOLD = 0.000040
|
|
93
|
+
elif not args.tight_convergence_criteria and args.loose_convergence_criteria:
|
|
94
|
+
self.MAX_FORCE_THRESHOLD = 0.0030
|
|
95
|
+
self.RMS_FORCE_THRESHOLD = 0.0020
|
|
96
|
+
self.MAX_DISPLACEMENT_THRESHOLD = 0.0100
|
|
97
|
+
self.RMS_DISPLACEMENT_THRESHOLD = 0.0070
|
|
98
|
+
else:
|
|
99
|
+
self.MAX_FORCE_THRESHOLD = 0.0003
|
|
100
|
+
self.RMS_FORCE_THRESHOLD = 0.0002
|
|
101
|
+
self.MAX_DISPLACEMENT_THRESHOLD = 0.0015
|
|
102
|
+
self.RMS_DISPLACEMENT_THRESHOLD = 0.0010
|
|
103
|
+
|
|
104
|
+
def _check_sub_basisset(self, args):
|
|
105
|
+
# Original _check_sub_basisset method code
|
|
106
|
+
if len(args.sub_basisset) % 2 != 0:
|
|
107
|
+
print("invalid input (-sub_bs)")
|
|
108
|
+
sys.exit(0)
|
|
109
|
+
self.electric_charge_and_multiplicity = [int(args.electronic_charge), int(args.spin_multiplicity)]
|
|
110
|
+
self.electronic_charge = args.electronic_charge
|
|
111
|
+
self.spin_multiplicity = args.spin_multiplicity
|
|
112
|
+
|
|
113
|
+
if args.pyscf:
|
|
114
|
+
self.SUB_BASIS_SET = {}
|
|
115
|
+
if len(args.sub_basisset) > 0:
|
|
116
|
+
self.SUB_BASIS_SET["default"] = str(self.BASIS_SET)
|
|
117
|
+
for j in range(int(len(args.sub_basisset) / 2)):
|
|
118
|
+
self.SUB_BASIS_SET[args.sub_basisset[2 * j]] = args.sub_basisset[2 * j + 1]
|
|
119
|
+
print("Basis Sets defined by User are detected.")
|
|
120
|
+
print(self.SUB_BASIS_SET)
|
|
121
|
+
else:
|
|
122
|
+
self.SUB_BASIS_SET = {"default": self.BASIS_SET}
|
|
123
|
+
else:
|
|
124
|
+
self.SUB_BASIS_SET = ""
|
|
125
|
+
if len(args.sub_basisset) > 0:
|
|
126
|
+
self.SUB_BASIS_SET += "\nassign " + str(self.BASIS_SET) + "\n"
|
|
127
|
+
for j in range(int(len(args.sub_basisset) / 2)):
|
|
128
|
+
self.SUB_BASIS_SET += "assign " + args.sub_basisset[2 * j] + " " + args.sub_basisset[2 * j + 1] + "\n"
|
|
129
|
+
print("Basis Sets defined by User are detected.")
|
|
130
|
+
print(self.SUB_BASIS_SET)
|
|
131
|
+
|
|
132
|
+
if len(args.effective_core_potential) % 2 != 0:
|
|
133
|
+
print("invaild input (-ecp)")
|
|
134
|
+
sys.exit(0)
|
|
135
|
+
|
|
136
|
+
if args.pyscf:
|
|
137
|
+
self.ECP = {}
|
|
138
|
+
if len(args.effective_core_potential) > 0:
|
|
139
|
+
for j in range(int(len(args.effective_core_potential)/2)):
|
|
140
|
+
self.ECP[args.effective_core_potential[2*j]] = args.effective_core_potential[2*j+1]
|
|
141
|
+
else:
|
|
142
|
+
self.ECP = ""
|
|
143
|
+
|
|
144
|
+
# Responsibility 2: Manages the "State" (mutable)
|
|
145
|
+
class OptimizationState:
|
|
146
|
+
"""
|
|
147
|
+
Holds all "state" variables that change during the optimization loop.
|
|
148
|
+
"""
|
|
149
|
+
def __init__(self, element_list):
|
|
150
|
+
natom = len(element_list)
|
|
151
|
+
|
|
152
|
+
# Current step state
|
|
153
|
+
self.iter = 0
|
|
154
|
+
self.e = None # Hartree
|
|
155
|
+
self.B_e = None # Hartree
|
|
156
|
+
self.g = None # Hartree/Bohr
|
|
157
|
+
self.B_g = None # Hartree/Bohr
|
|
158
|
+
self.geom_num_list = None # Bohr
|
|
159
|
+
self.Model_hess = np.eye(natom * 3) # Model_hess is treated as state
|
|
160
|
+
self.element_list = element_list
|
|
161
|
+
|
|
162
|
+
# Previous step state
|
|
163
|
+
self.pre_e = 0.0
|
|
164
|
+
self.pre_B_e = 0.0
|
|
165
|
+
self.pre_geom = np.zeros((natom, 3), dtype="float64")
|
|
166
|
+
self.pre_g = np.zeros((natom, 3), dtype="float64")
|
|
167
|
+
self.pre_B_g = np.zeros((natom, 3), dtype="float64")
|
|
168
|
+
self.pre_move_vector = np.zeros((natom, 3), dtype="float64")
|
|
169
|
+
|
|
170
|
+
# Plotting / result lists
|
|
171
|
+
self.ENERGY_LIST_FOR_PLOTTING = []
|
|
172
|
+
self.BIAS_ENERGY_LIST_FOR_PLOTTING = []
|
|
173
|
+
self.NUM_LIST = []
|
|
174
|
+
self.grad_list = []
|
|
175
|
+
self.bias_grad_list = []
|
|
176
|
+
self.cos_list = [] # Initialized properly in Optimize class
|
|
177
|
+
|
|
178
|
+
# Final result placeholders
|
|
179
|
+
self.final_file_directory = None
|
|
180
|
+
self.final_geometry = None
|
|
181
|
+
self.final_energy = None
|
|
182
|
+
self.final_bias_energy = None
|
|
183
|
+
self.bias_pot_params_grad_list = None
|
|
184
|
+
self.bias_pot_params_grad_name_list = None
|
|
185
|
+
|
|
186
|
+
# Flags
|
|
187
|
+
self.DC_check_flag = False
|
|
188
|
+
self.optimized_flag = False
|
|
189
|
+
self.exit_flag = False
|
|
190
|
+
|
|
191
|
+
# Responsibility 3: Performs the "Execution" (main logic)
|
|
192
|
+
class Optimize:
|
|
193
|
+
"""
|
|
194
|
+
Main execution (Runner) class.
|
|
195
|
+
It holds the Config, creates and updates the State,
|
|
196
|
+
and runs the main optimization logic.
|
|
197
|
+
"""
|
|
198
|
+
def __init__(self, args):
|
|
199
|
+
# 1. Set the Configuration (immutable)
|
|
200
|
+
self.config = OptimizationConfig(args)
|
|
201
|
+
|
|
202
|
+
# 2. State will be created freshly inside the run() method for each job.
|
|
203
|
+
self.state = None
|
|
204
|
+
|
|
205
|
+
# 3. Helper instances and job-specific variables
|
|
206
|
+
self.BPA_FOLDER_DIRECTORY = None
|
|
207
|
+
self.START_FILE = None
|
|
208
|
+
self.element_list = None # Will be set in run()
|
|
209
|
+
self.CalcBiaspot = None # Shared helper
|
|
210
|
+
self.SP = None # Shared helper
|
|
211
|
+
|
|
212
|
+
# 4. Final results (for external access, mirrors original design)
|
|
213
|
+
self.final_file_directory = None
|
|
214
|
+
self.final_geometry = None
|
|
215
|
+
self.final_energy = None
|
|
216
|
+
self.final_bias_energy = None
|
|
217
|
+
self.irc_terminal_struct_paths = []
|
|
218
|
+
self.optimized_struct_file = None
|
|
219
|
+
self.traj_file = None
|
|
220
|
+
self.symmetry = None
|
|
221
|
+
|
|
222
|
+
# --- Helper Methods ---
|
|
223
|
+
# (Ported from the original class)
|
|
224
|
+
# These methods must now read from self.config
|
|
225
|
+
# and read/write from self.state.
|
|
226
|
+
|
|
227
|
+
def _make_init_directory(self, file):
|
|
228
|
+
"""
|
|
229
|
+
Create initial directory for optimization results.
|
|
230
|
+
Uses self.config to build the path.
|
|
231
|
+
"""
|
|
232
|
+
self.START_FILE = file
|
|
233
|
+
timestamp = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_%S_%f")[:-2]
|
|
234
|
+
date = datetime.datetime.now().strftime("%Y_%m_%d")
|
|
235
|
+
base_dir = f"{date}/{self.START_FILE[:-4]}_OPT_"
|
|
236
|
+
|
|
237
|
+
if self.config.othersoft != "None":
|
|
238
|
+
self.BPA_FOLDER_DIRECTORY = f"{base_dir}ASE_{timestamp}/"
|
|
239
|
+
elif self.config.sqm2:
|
|
240
|
+
self.BPA_FOLDER_DIRECTORY = f"{base_dir}SQM2_{timestamp}/"
|
|
241
|
+
elif self.config.sqm1:
|
|
242
|
+
self.BPA_FOLDER_DIRECTORY = f"{base_dir}SQM1_{timestamp}/"
|
|
243
|
+
elif self.config.args.usextb == "None" and self.config.args.usedxtb == "None":
|
|
244
|
+
self.BPA_FOLDER_DIRECTORY = f"{base_dir}{self.config.FUNCTIONAL}_{self.config.BASIS_SET}_{timestamp}/"
|
|
245
|
+
else:
|
|
246
|
+
method = self.config.args.usedxtb if self.config.args.usedxtb != "None" else self.config.args.usextb
|
|
247
|
+
self.BPA_FOLDER_DIRECTORY = f"{base_dir}{method}_{timestamp}/"
|
|
248
|
+
|
|
249
|
+
os.makedirs(self.BPA_FOLDER_DIRECTORY, exist_ok=True)
|
|
250
|
+
|
|
251
|
+
def _save_input_data(self):
|
|
252
|
+
with open(self.BPA_FOLDER_DIRECTORY+"input.txt", "w") as f:
|
|
253
|
+
f.write(str(vars(self.config.args))) # Read from config
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
def _constrain_flag_check(self, force_data):
|
|
257
|
+
# (This method is pure, no changes needed)
|
|
258
|
+
if len(force_data["projection_constraint_condition_list"]) > 0:
|
|
259
|
+
projection_constrain = True
|
|
260
|
+
else:
|
|
261
|
+
projection_constrain = False
|
|
262
|
+
|
|
263
|
+
if len(force_data["fix_atoms"]) == 0:
|
|
264
|
+
allactive_flag = True
|
|
265
|
+
else:
|
|
266
|
+
allactive_flag = False
|
|
267
|
+
|
|
268
|
+
if "x" in force_data["projection_constraint_condition_list"] or "y" in force_data["projection_constraint_condition_list"] or "z" in force_data["projection_constraint_condition_list"]:
|
|
269
|
+
allactive_flag = False
|
|
270
|
+
|
|
271
|
+
return projection_constrain, allactive_flag
|
|
272
|
+
|
|
273
|
+
def _init_projection_constraint(self, PC, geom_num_list, iter, projection_constrain, hessian=None):
|
|
274
|
+
# (This method is pure, no changes needed)
|
|
275
|
+
if iter == 0:
|
|
276
|
+
if projection_constrain:
|
|
277
|
+
PC.initialize(geom_num_list, hessian=hessian)
|
|
278
|
+
else:
|
|
279
|
+
pass
|
|
280
|
+
return PC
|
|
281
|
+
else:
|
|
282
|
+
return PC
|
|
283
|
+
|
|
284
|
+
def _save_init_geometry(self, geom_num_list, element_list, allactive_flag):
|
|
285
|
+
# (This method is pure, no changes needed)
|
|
286
|
+
if allactive_flag:
|
|
287
|
+
initial_geom_num_list = geom_num_list - Calculationtools().calc_center(geom_num_list, element_list)
|
|
288
|
+
pre_geom = initial_geom_num_list - Calculationtools().calc_center(geom_num_list, element_list)
|
|
289
|
+
else:
|
|
290
|
+
initial_geom_num_list = geom_num_list
|
|
291
|
+
pre_geom = initial_geom_num_list
|
|
292
|
+
|
|
293
|
+
return initial_geom_num_list, pre_geom
|
|
294
|
+
|
|
295
|
+
def _calc_eff_hess_for_fix_atoms_and_set_hess(self, allactive_flag, force_data, BPA_hessian, n_fix, optimizer_instances, geom_num_list, B_g, g, projection_constrain, PC):
|
|
296
|
+
# (Reads self.state.Model_hess, self.config.FC_COUNT, etc.)
|
|
297
|
+
if not allactive_flag:
|
|
298
|
+
fix_num = []
|
|
299
|
+
for fnum in force_data["fix_atoms"]:
|
|
300
|
+
fix_num.extend([3*(fnum-1)+0, 3*(fnum-1)+1, 3*(fnum-1)+2])
|
|
301
|
+
fix_num = np.array(fix_num, dtype="int64")
|
|
302
|
+
#effective hessian
|
|
303
|
+
tmp_fix_hess = self.state.Model_hess[np.ix_(fix_num, fix_num)] + np.eye((3*n_fix)) * 1e-10
|
|
304
|
+
inv_tmp_fix_hess = np.linalg.pinv(tmp_fix_hess)
|
|
305
|
+
tmp_fix_bias_hess = BPA_hessian[np.ix_(fix_num, fix_num)] + np.eye((3*n_fix)) * 1e-10
|
|
306
|
+
inv_tmp_fix_bias_hess = np.linalg.pinv(tmp_fix_bias_hess)
|
|
307
|
+
BPA_hessian -= np.dot(BPA_hessian[:, fix_num], np.dot(inv_tmp_fix_bias_hess, BPA_hessian[fix_num, :]))
|
|
308
|
+
|
|
309
|
+
for i in range(len(optimizer_instances)):
|
|
310
|
+
|
|
311
|
+
if projection_constrain:
|
|
312
|
+
if np.all(np.abs(BPA_hessian) < 1e-20):
|
|
313
|
+
proj_bpa_hess = PC.calc_project_out_hess(geom_num_list, B_g - g, BPA_hessian)
|
|
314
|
+
else:
|
|
315
|
+
proj_bpa_hess = BPA_hessian
|
|
316
|
+
optimizer_instances[i].set_bias_hessian(proj_bpa_hess)
|
|
317
|
+
else:
|
|
318
|
+
optimizer_instances[i].set_bias_hessian(BPA_hessian)
|
|
319
|
+
|
|
320
|
+
if self.state.iter % self.config.FC_COUNT == 0 or (self.config.use_model_hessian is not None and self.state.iter % self.config.mFC_COUNT == 0):
|
|
321
|
+
|
|
322
|
+
if not allactive_flag:
|
|
323
|
+
self.state.Model_hess -= np.dot(self.state.Model_hess[:, fix_num], np.dot(inv_tmp_fix_hess, self.state.Model_hess[fix_num, :]))
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
if projection_constrain:
|
|
327
|
+
proj_model_hess = PC.calc_project_out_hess(geom_num_list, g, self.state.Model_hess)
|
|
328
|
+
optimizer_instances[i].set_hessian(proj_model_hess)
|
|
329
|
+
else:
|
|
330
|
+
optimizer_instances[i].set_hessian(self.state.Model_hess)
|
|
331
|
+
|
|
332
|
+
return optimizer_instances
|
|
333
|
+
|
|
334
|
+
def _apply_projection_constraints(self, projection_constrain, PC, geom_num_list, g, B_g):
|
|
335
|
+
# (This method is pure, no changes needed)
|
|
336
|
+
if projection_constrain:
|
|
337
|
+
g = copy.deepcopy(PC.calc_project_out_grad(geom_num_list, g))
|
|
338
|
+
proj_d_B_g = copy.deepcopy(PC.calc_project_out_grad(geom_num_list, B_g - g))
|
|
339
|
+
B_g = copy.deepcopy(g + proj_d_B_g)
|
|
340
|
+
|
|
341
|
+
return g, B_g, PC
|
|
342
|
+
|
|
343
|
+
def _zero_fixed_atom_gradients(self, allactive_flag, force_data, g, B_g):
|
|
344
|
+
# (This method is pure, no changes needed)
|
|
345
|
+
if not allactive_flag:
|
|
346
|
+
for j in force_data["fix_atoms"]:
|
|
347
|
+
g[j-1] = copy.deepcopy(g[j-1]*0.0)
|
|
348
|
+
B_g[j-1] = copy.deepcopy(B_g[j-1]*0.0)
|
|
349
|
+
|
|
350
|
+
return g, B_g
|
|
351
|
+
|
|
352
|
+
def _project_out_translation_rotation(self, new_geometry, geom_num_list, allactive_flag):
|
|
353
|
+
# (Reads self.config.bohr2angstroms)
|
|
354
|
+
if allactive_flag:
|
|
355
|
+
# Convert to Bohr, apply Kabsch alignment algorithm, then convert back
|
|
356
|
+
aligned_geometry, _ = Calculationtools().kabsch_algorithm(
|
|
357
|
+
new_geometry/self.config.bohr2angstroms, geom_num_list)
|
|
358
|
+
aligned_geometry *= self.config.bohr2angstroms
|
|
359
|
+
return aligned_geometry
|
|
360
|
+
else:
|
|
361
|
+
# If not all atoms are active, return the original geometry
|
|
362
|
+
return new_geometry
|
|
363
|
+
|
|
364
|
+
def _apply_projection_constraints_to_geometry(self, projection_constrain, PC, new_geometry, hessian=None):
|
|
365
|
+
# (Reads self.config.bohr2angstroms)
|
|
366
|
+
if projection_constrain:
|
|
367
|
+
tmp_new_geometry = new_geometry / self.config.bohr2angstroms
|
|
368
|
+
adjusted_geometry = PC.adjust_init_coord(tmp_new_geometry, hessian=hessian) * self.config.bohr2angstroms
|
|
369
|
+
return adjusted_geometry, PC
|
|
370
|
+
|
|
371
|
+
return new_geometry, PC
|
|
372
|
+
|
|
373
|
+
def _reset_fixed_atom_positions(self, new_geometry, initial_geom_num_list, allactive_flag, force_data):
|
|
374
|
+
# (Reads self.config.bohr2angstroms)
|
|
375
|
+
if not allactive_flag:
|
|
376
|
+
for j in force_data["fix_atoms"]:
|
|
377
|
+
new_geometry[j-1] = copy.deepcopy(initial_geom_num_list[j-1]*self.config.bohr2angstroms)
|
|
378
|
+
|
|
379
|
+
return new_geometry
|
|
380
|
+
|
|
381
|
+
def _initialize_optimization_tools(self, FIO, force_data):
|
|
382
|
+
"""
|
|
383
|
+
Initializes all tools needed for the optimization loop.
|
|
384
|
+
This replaces the old _initialize_optimization_variables.
|
|
385
|
+
It assumes self.state is already created.
|
|
386
|
+
"""
|
|
387
|
+
# Load modules
|
|
388
|
+
Calculation, xtb_method = self._import_calculation_module()
|
|
389
|
+
self._save_input_data() # Save input.txt
|
|
390
|
+
G = Graph(self.BPA_FOLDER_DIRECTORY)
|
|
391
|
+
|
|
392
|
+
# Get atom info
|
|
393
|
+
file_directory, electric_charge_and_multiplicity, element_list = self.write_input_files(FIO)
|
|
394
|
+
self.element_list = element_list # Store on self for helper methods
|
|
395
|
+
self.state.element_list = element_list # Store in state
|
|
396
|
+
|
|
397
|
+
element_number_list = np.array([element_number(elem) for elem in element_list], dtype="int")
|
|
398
|
+
natom = len(element_list)
|
|
399
|
+
|
|
400
|
+
# Constraint setup
|
|
401
|
+
PC = ProjectOutConstrain(force_data["projection_constraint_condition_list"],
|
|
402
|
+
force_data["projection_constraint_atoms"],
|
|
403
|
+
force_data["projection_constraint_constant"])
|
|
404
|
+
projection_constrain, allactive_flag = self._constrain_flag_check(force_data)
|
|
405
|
+
n_fix = len(force_data["fix_atoms"])
|
|
406
|
+
|
|
407
|
+
# Bias potential and calculation setup
|
|
408
|
+
self.CalcBiaspot = BiasPotentialCalculation(self.BPA_FOLDER_DIRECTORY)
|
|
409
|
+
self.SP = self.setup_calculation(Calculation) # SP is self.SP
|
|
410
|
+
|
|
411
|
+
# Move vector calculation
|
|
412
|
+
CMV = CalculateMoveVector(self.config.DELTA, element_list, self.config.args.saddle_order,
|
|
413
|
+
self.config.FC_COUNT, self.config.temperature, self.config.use_model_hessian,
|
|
414
|
+
max_trust_radius=self.config.max_trust_radius, min_trust_radius=self.config.min_trust_radius)
|
|
415
|
+
optimizer_instances = CMV.initialization(force_data["opt_method"])
|
|
416
|
+
|
|
417
|
+
# Check optimizer compatibility
|
|
418
|
+
for i in range(len(optimizer_instances)):
|
|
419
|
+
if CMV.newton_tag[i] is False and self.config.FC_COUNT > 0 and not "eigvec" in force_data["projection_constraint_condition_list"]:
|
|
420
|
+
print("Error: This optimizer method does not support exact Hessian calculations.")
|
|
421
|
+
print("Please either choose a different optimizer or set FC_COUNT=0 to disable exact Hessian calculations.")
|
|
422
|
+
sys.exit(0)
|
|
423
|
+
|
|
424
|
+
# Initialize optimizer instances
|
|
425
|
+
for i in range(len(optimizer_instances)):
|
|
426
|
+
optimizer_instances[i].set_hessian(self.state.Model_hess) # From state
|
|
427
|
+
if self.config.DELTA != "x":
|
|
428
|
+
optimizer_instances[i].DELTA = self.config.DELTA
|
|
429
|
+
|
|
430
|
+
if self.config.koopman_analysis:
|
|
431
|
+
KA = KoopmanAnalyzer(natom, file_directory=self.BPA_FOLDER_DIRECTORY)
|
|
432
|
+
else:
|
|
433
|
+
KA = None
|
|
434
|
+
|
|
435
|
+
# Pack and return all initialized tools
|
|
436
|
+
tools = {
|
|
437
|
+
'Calculation': Calculation, 'xtb_method': xtb_method,
|
|
438
|
+
'SP': self.SP, 'CMV': CMV, 'optimizer_instances': optimizer_instances,
|
|
439
|
+
'FIO': FIO, 'G': G, 'file_directory': file_directory,
|
|
440
|
+
'element_number_list': element_number_list, 'natom': natom,
|
|
441
|
+
'electric_charge_and_multiplicity': electric_charge_and_multiplicity,
|
|
442
|
+
'PC': PC, 'projection_constrain': projection_constrain,
|
|
443
|
+
'allactive_flag': allactive_flag, 'force_data': force_data, 'n_fix': n_fix,
|
|
444
|
+
'KA': KA
|
|
445
|
+
}
|
|
446
|
+
return tools
|
|
447
|
+
|
|
448
|
+
def check_negative_eigenvalues(self, geom_num_list, hessian):
|
|
449
|
+
# (This method is pure, no changes needed)
|
|
450
|
+
proj_hessian = Calculationtools().project_out_hess_tr_and_rot_for_coord(hessian, geom_num_list, geom_num_list, display_eigval=False)
|
|
451
|
+
if proj_hessian is not None:
|
|
452
|
+
eigvals = np.linalg.eigvalsh(proj_hessian)
|
|
453
|
+
if np.any(eigvals < -1e-10):
|
|
454
|
+
print("Notice: Negative eigenvalues detected.")
|
|
455
|
+
return True
|
|
456
|
+
return False
|
|
457
|
+
|
|
458
|
+
def judge_early_stop_due_to_no_negative_eigenvalues(self, geom_num_list, hessian):
|
|
459
|
+
# (Reads self.config)
|
|
460
|
+
if self.config.detect_negative_eigenvalues and self.config.FC_COUNT > 0:
|
|
461
|
+
negative_eigenvalues_detected = self.check_negative_eigenvalues(geom_num_list, hessian)
|
|
462
|
+
if not negative_eigenvalues_detected and self.config.args.saddle_order > 0:
|
|
463
|
+
print("No negative eigenvalues detected while saddle_order > 0. Stopping optimization.")
|
|
464
|
+
with open(self.BPA_FOLDER_DIRECTORY+"no_negative_eigenvalues_detected.txt", "w") as f:
|
|
465
|
+
f.write("No negative eigenvalues detected while saddle_order > 0. Stopping optimization.")
|
|
466
|
+
return True
|
|
467
|
+
return False
|
|
468
|
+
|
|
469
|
+
def optimize(self):
|
|
470
|
+
# 1. Initialize State.
|
|
471
|
+
# write_input_files needs FIO, FIO needs BPA_FOLDER_DIRECTORY.
|
|
472
|
+
# This is complex. Let's initialize FIO and element_list first.
|
|
473
|
+
FIO = FileIO(self.BPA_FOLDER_DIRECTORY, self.START_FILE)
|
|
474
|
+
|
|
475
|
+
# This will read the file and set self.element_list
|
|
476
|
+
file_directory, electric_charge_and_multiplicity, element_list = self.write_input_files(FIO)
|
|
477
|
+
self.element_list = element_list
|
|
478
|
+
|
|
479
|
+
# Now we can create the State
|
|
480
|
+
self.state = OptimizationState(element_list)
|
|
481
|
+
self.state.cos_list = [[] for i in range(len(force_data_parser(self.config.args)["geom_info"]))] # Init cos_list
|
|
482
|
+
|
|
483
|
+
# 2. Initialize all other tools, passing FIO
|
|
484
|
+
force_data = force_data_parser(self.config.args)
|
|
485
|
+
tools = self._initialize_optimization_tools(FIO, force_data)
|
|
486
|
+
|
|
487
|
+
# 3. Unpack tools into local variables for the loop
|
|
488
|
+
# (This is better than the giant vars_dict at the end)
|
|
489
|
+
xtb_method = tools['xtb_method']
|
|
490
|
+
SP = tools['SP']
|
|
491
|
+
CMV = tools['CMV']
|
|
492
|
+
optimizer_instances = tools['optimizer_instances']
|
|
493
|
+
FIO = tools['FIO']
|
|
494
|
+
G = tools['G']
|
|
495
|
+
file_directory = tools['file_directory']
|
|
496
|
+
element_number_list = tools['element_number_list']
|
|
497
|
+
electric_charge_and_multiplicity = tools['electric_charge_and_multiplicity']
|
|
498
|
+
PC = tools['PC']
|
|
499
|
+
projection_constrain = tools['projection_constrain']
|
|
500
|
+
allactive_flag = tools['allactive_flag']
|
|
501
|
+
force_data = tools['force_data']
|
|
502
|
+
n_fix = tools['n_fix']
|
|
503
|
+
KA = tools['KA']
|
|
504
|
+
|
|
505
|
+
# 4. Main Optimization Loop
|
|
506
|
+
for iter in range(self.config.NSTEP):
|
|
507
|
+
|
|
508
|
+
self.state.iter = iter
|
|
509
|
+
self.state.exit_flag = os.path.exists(self.BPA_FOLDER_DIRECTORY+"end.txt")
|
|
510
|
+
if self.state.exit_flag:
|
|
511
|
+
break
|
|
512
|
+
|
|
513
|
+
self.state.exit_flag = judge_shape_condition(self.state.geom_num_list, self.config.shape_conditions)
|
|
514
|
+
if self.state.exit_flag:
|
|
515
|
+
break
|
|
516
|
+
|
|
517
|
+
print(f"\n# ITR. {iter}\n")
|
|
518
|
+
|
|
519
|
+
# --- Perform Single Point Calculation ---
|
|
520
|
+
SP.Model_hess = copy.deepcopy(self.state.Model_hess)
|
|
521
|
+
e, g, geom_num_list, exit_flag = SP.single_point(file_directory, element_number_list, iter, electric_charge_and_multiplicity, xtb_method)
|
|
522
|
+
|
|
523
|
+
# Update state
|
|
524
|
+
self.state.e = e
|
|
525
|
+
self.state.g = g
|
|
526
|
+
self.state.geom_num_list = geom_num_list
|
|
527
|
+
self.state.exit_flag = exit_flag
|
|
528
|
+
self.state.Model_hess = copy.deepcopy(SP.Model_hess)
|
|
529
|
+
|
|
530
|
+
if self.state.exit_flag:
|
|
531
|
+
break
|
|
532
|
+
|
|
533
|
+
# --- Update Model Hessian (if needed) ---
|
|
534
|
+
if iter % self.config.mFC_COUNT == 0 and self.config.use_model_hessian is not None and self.config.FC_COUNT < 1:
|
|
535
|
+
SP.Model_hess = ApproxHessian().main(geom_num_list, self.element_list, g, self.config.use_model_hessian)
|
|
536
|
+
self.state.Model_hess = SP.Model_hess
|
|
537
|
+
|
|
538
|
+
if iter == 0:
|
|
539
|
+
initial_geom_num_list, pre_geom = self._save_init_geometry(geom_num_list, self.element_list, allactive_flag)
|
|
540
|
+
# Save initial geometry to state
|
|
541
|
+
self.state.pre_geom = pre_geom
|
|
542
|
+
|
|
543
|
+
# --- Bias Potential Calculation ---
|
|
544
|
+
_, B_e, B_g, BPA_hessian = self.CalcBiaspot.main(e, g, geom_num_list, self.element_list, force_data, self.state.pre_B_g, iter, initial_geom_num_list)
|
|
545
|
+
# Update state
|
|
546
|
+
self.state.B_e = B_e
|
|
547
|
+
self.state.B_g = B_g
|
|
548
|
+
|
|
549
|
+
# --- Check Eigenvalues (if first iter) ---
|
|
550
|
+
Hess = BPA_hessian + self.state.Model_hess
|
|
551
|
+
if iter == 0:
|
|
552
|
+
if self.judge_early_stop_due_to_no_negative_eigenvalues(geom_num_list, Hess):
|
|
553
|
+
break
|
|
554
|
+
|
|
555
|
+
# --- Constraints ---
|
|
556
|
+
PC = self._init_projection_constraint(PC, geom_num_list, iter, projection_constrain, hessian=Hess)
|
|
557
|
+
optimizer_instances = self._calc_eff_hess_for_fix_atoms_and_set_hess(allactive_flag, force_data, BPA_hessian, n_fix, optimizer_instances, geom_num_list, B_g, g, projection_constrain, PC)
|
|
558
|
+
|
|
559
|
+
if not allactive_flag:
|
|
560
|
+
B_g = copy.deepcopy(self.calc_fragement_grads(B_g, force_data["opt_fragment"]))
|
|
561
|
+
g = copy.deepcopy(self.calc_fragement_grads(g, force_data["opt_fragment"]))
|
|
562
|
+
|
|
563
|
+
self.save_tmp_energy_profiles(iter, e, g, B_g)
|
|
564
|
+
|
|
565
|
+
g, B_g, PC = self._apply_projection_constraints(projection_constrain, PC, geom_num_list, g, B_g)
|
|
566
|
+
g, B_g = self._zero_fixed_atom_gradients(allactive_flag, force_data, g, B_g)
|
|
567
|
+
|
|
568
|
+
# Update state with final gradients for this step
|
|
569
|
+
self.state.g = g
|
|
570
|
+
self.state.B_g = B_g
|
|
571
|
+
|
|
572
|
+
if self.config.koopman_analysis:
|
|
573
|
+
_ = KA.run(iter, geom_num_list, B_g, self.element_list)
|
|
574
|
+
|
|
575
|
+
# --- Calculate Move Vector ---
|
|
576
|
+
new_geometry, move_vector, optimizer_instances = CMV.calc_move_vector(
|
|
577
|
+
iter, geom_num_list, B_g, self.state.pre_B_g, self.state.pre_geom, B_e, self.state.pre_B_e,
|
|
578
|
+
self.state.pre_move_vector, initial_geom_num_list, g, self.state.pre_g, optimizer_instances, projection_constrain)
|
|
579
|
+
|
|
580
|
+
# --- Post-step Geometry Adjustments ---
|
|
581
|
+
new_geometry = self._project_out_translation_rotation(new_geometry, geom_num_list, allactive_flag)
|
|
582
|
+
new_geometry, PC = self._apply_projection_constraints_to_geometry(projection_constrain, PC, new_geometry, hessian=Hess)
|
|
583
|
+
|
|
584
|
+
# --- Update State Lists ---
|
|
585
|
+
self.state.ENERGY_LIST_FOR_PLOTTING.append(e * self.config.hartree2kcalmol)
|
|
586
|
+
self.state.BIAS_ENERGY_LIST_FOR_PLOTTING.append(B_e * self.config.hartree2kcalmol)
|
|
587
|
+
self.state.NUM_LIST.append(int(iter))
|
|
588
|
+
|
|
589
|
+
self.geom_info_extract(force_data, file_directory, B_g, g) # This updates self.state.cos_list
|
|
590
|
+
|
|
591
|
+
if self.state.iter == 0:
|
|
592
|
+
displacement_vector = move_vector
|
|
593
|
+
else:
|
|
594
|
+
displacement_vector = new_geometry / self.config.bohr2angstroms - geom_num_list
|
|
595
|
+
|
|
596
|
+
# --- Check Convergence ---
|
|
597
|
+
converge_flag, max_displacement_threshold, rms_displacement_threshold = self._check_converge_criteria(B_g, displacement_vector)
|
|
598
|
+
self.print_info(e, B_e, B_g, displacement_vector, self.state.pre_e, self.state.pre_B_e, max_displacement_threshold, rms_displacement_threshold)
|
|
599
|
+
|
|
600
|
+
self.state.grad_list.append(self.calculate_rms_safely(g))
|
|
601
|
+
self.state.bias_grad_list.append(self.calculate_rms_safely(B_g))
|
|
602
|
+
|
|
603
|
+
new_geometry = self._reset_fixed_atom_positions(new_geometry, initial_geom_num_list, allactive_flag, force_data)
|
|
604
|
+
|
|
605
|
+
# --- Dissociation Check ---
|
|
606
|
+
DC_exit_flag = self.dissociation_check(new_geometry, self.element_list)
|
|
607
|
+
|
|
608
|
+
if converge_flag:
|
|
609
|
+
if projection_constrain and iter == 0:
|
|
610
|
+
pass
|
|
611
|
+
else:
|
|
612
|
+
self.state.optimized_flag = True
|
|
613
|
+
print("\n=====================================================")
|
|
614
|
+
print("converged!!!")
|
|
615
|
+
print("=====================================================")
|
|
616
|
+
break
|
|
617
|
+
|
|
618
|
+
if DC_exit_flag:
|
|
619
|
+
self.state.DC_check_flag = True
|
|
620
|
+
break
|
|
621
|
+
|
|
622
|
+
# --- Save State for Next Iteration ---
|
|
623
|
+
self.state.pre_B_e = B_e
|
|
624
|
+
self.state.pre_e = e
|
|
625
|
+
self.state.pre_B_g = B_g
|
|
626
|
+
self.state.pre_g = g
|
|
627
|
+
self.state.pre_geom = geom_num_list
|
|
628
|
+
self.state.pre_move_vector = move_vector
|
|
629
|
+
|
|
630
|
+
# --- Write Next Input File ---
|
|
631
|
+
geometry_list = FIO.print_geometry_list(new_geometry, self.element_list, electric_charge_and_multiplicity)
|
|
632
|
+
file_directory = FIO.make_psi4_input_file(geometry_list, iter+1)
|
|
633
|
+
|
|
634
|
+
else: # Loop ended (no break)
|
|
635
|
+
self.state.optimized_flag = False
|
|
636
|
+
print("Reached maximum number of iterations. This is not converged.")
|
|
637
|
+
with open(self.BPA_FOLDER_DIRECTORY+"not_converged.txt", "w") as f:
|
|
638
|
+
f.write("Reached maximum number of iterations. This is not converged.")
|
|
639
|
+
|
|
640
|
+
# --- 5. Post-Optimization Analysis ---
|
|
641
|
+
|
|
642
|
+
# Check if exact hessian is already computed.
|
|
643
|
+
if self.config.FC_COUNT == -1:
|
|
644
|
+
exact_hess_flag = False
|
|
645
|
+
elif self.state.iter % self.config.FC_COUNT == 0 and self.config.FC_COUNT > 0:
|
|
646
|
+
exact_hess_flag = True
|
|
647
|
+
else:
|
|
648
|
+
exact_hess_flag = False
|
|
649
|
+
|
|
650
|
+
if self.state.DC_check_flag:
|
|
651
|
+
print("Dissociation is detected. Optimization stopped.")
|
|
652
|
+
with open(self.BPA_FOLDER_DIRECTORY+"dissociation_is_detected.txt", "w") as f:
|
|
653
|
+
f.write("Dissociation is detected. Optimization stopped.")
|
|
654
|
+
|
|
655
|
+
if self.config.freq_analysis and not self.state.exit_flag and not self.state.DC_check_flag:
|
|
656
|
+
self._perform_vibrational_analysis(SP, geom_num_list, self.element_list, initial_geom_num_list, force_data, exact_hess_flag, file_directory, iter, electric_charge_and_multiplicity, xtb_method, e)
|
|
657
|
+
|
|
658
|
+
# --- 6. Finalize and Save Results ---
|
|
659
|
+
self._finalize_optimization(FIO, G, self.state.grad_list, self.state.bias_grad_list, file_directory, force_data, geom_num_list, e, B_e, SP, self.state.exit_flag)
|
|
660
|
+
|
|
661
|
+
# Copy final results from state to self
|
|
662
|
+
self._copy_final_results_from_state()
|
|
663
|
+
return
|
|
664
|
+
|
|
665
|
+
def _perform_vibrational_analysis(self, SP, geom_num_list, element_list, initial_geom_num_list, force_data, exact_hess_flag, file_directory, iter, electric_charge_and_multiplicity, xtb_method, e):
|
|
666
|
+
# (Reads self.state, self.config)
|
|
667
|
+
print("\n====================================================")
|
|
668
|
+
print("Performing vibrational analysis...")
|
|
669
|
+
print("====================================================\n")
|
|
670
|
+
print("Is Exact Hessian calculated? : ", exact_hess_flag)
|
|
671
|
+
|
|
672
|
+
if exact_hess_flag:
|
|
673
|
+
g = np.zeros_like(geom_num_list, dtype="float64")
|
|
674
|
+
exit_flag = False
|
|
675
|
+
else:
|
|
676
|
+
print("Calculate exact Hessian...")
|
|
677
|
+
SP.hessian_flag = True
|
|
678
|
+
e, g, geom_num_list, exit_flag = SP.single_point(file_directory, element_list, iter, electric_charge_and_multiplicity, xtb_method)
|
|
679
|
+
SP.hessian_flag = False
|
|
680
|
+
|
|
681
|
+
if exit_flag:
|
|
682
|
+
print("Error: QM calculation failed.")
|
|
683
|
+
return
|
|
684
|
+
|
|
685
|
+
_, B_e, _, BPA_hessian = self.CalcBiaspot.main(e, g, geom_num_list, element_list, force_data, pre_B_g="", iter=iter, initial_geom_num_list="")
|
|
686
|
+
tmp_hess = copy.deepcopy(SP.Model_hess) # SP.Model_hess holds the latest hessian
|
|
687
|
+
tmp_hess += BPA_hessian
|
|
688
|
+
|
|
689
|
+
MV = MolecularVibrations(atoms=element_list, coordinates=geom_num_list, hessian=tmp_hess)
|
|
690
|
+
results = MV.calculate_thermochemistry(e_tot=B_e, temperature=self.config.thermo_temperature, pressure=self.config.thermo_pressure)
|
|
691
|
+
|
|
692
|
+
MV.print_thermochemistry(output_file=self.BPA_FOLDER_DIRECTORY+"/thermochemistry.txt")
|
|
693
|
+
MV.print_normal_modes(output_file=self.BPA_FOLDER_DIRECTORY+"/normal_modes.txt")
|
|
694
|
+
MV.create_vibration_animation(output_dir=self.BPA_FOLDER_DIRECTORY+"/vibration_animation")
|
|
695
|
+
|
|
696
|
+
if not self.state.optimized_flag:
|
|
697
|
+
print("Warning: Vibrational analysis was performed, but the optimization did not converge. The result of thermochemistry is useless.")
|
|
698
|
+
|
|
699
|
+
return
|
|
700
|
+
|
|
701
|
+
def _finalize_optimization(self, FIO, G, grad_list, bias_grad_list, file_directory, force_data, geom_num_list, e, B_e, SP, exit_flag):
|
|
702
|
+
# (Writes to self.state)
|
|
703
|
+
self._save_opt_results(FIO, G, grad_list, bias_grad_list, file_directory, force_data)
|
|
704
|
+
|
|
705
|
+
self.state.bias_pot_params_grad_list = self.CalcBiaspot.bias_pot_params_grad_list
|
|
706
|
+
self.state.bias_pot_params_grad_name_list = self.CalcBiaspot.bias_pot_params_grad_name_list
|
|
707
|
+
self.state.final_file_directory = file_directory
|
|
708
|
+
self.state.final_geometry = geom_num_list # Bohr
|
|
709
|
+
self.state.final_energy = e # Hartree
|
|
710
|
+
self.state.final_bias_energy = B_e # Hartree
|
|
711
|
+
|
|
712
|
+
if not exit_flag:
|
|
713
|
+
self.symmetry = analyze_symmetry(self.element_list, self.state.final_geometry)
|
|
714
|
+
self.state.symmetry = self.symmetry # Save to state too
|
|
715
|
+
with open(self.BPA_FOLDER_DIRECTORY+"symmetry.txt", "w") as f:
|
|
716
|
+
f.write(f"Symmetry of final structure: {self.symmetry}")
|
|
717
|
+
print(f"Symmetry: {self.symmetry}")
|
|
718
|
+
|
|
719
|
+
def _save_opt_results(self, FIO, G, grad_list, bias_grad_list, file_directory, force_data):
|
|
720
|
+
# (Reads self.state)
|
|
721
|
+
G.double_plot(self.state.NUM_LIST, self.state.ENERGY_LIST_FOR_PLOTTING, self.state.BIAS_ENERGY_LIST_FOR_PLOTTING)
|
|
722
|
+
G.single_plot(self.state.NUM_LIST, grad_list, file_directory, "", axis_name_2="gradient (RMS) [a.u.]", name="gradient")
|
|
723
|
+
G.single_plot(self.state.NUM_LIST, bias_grad_list, file_directory, "", axis_name_2="bias gradient (RMS) [a.u.]", name="bias_gradient")
|
|
724
|
+
|
|
725
|
+
if len(force_data["geom_info"]) > 1:
|
|
726
|
+
for num, i in enumerate(force_data["geom_info"]):
|
|
727
|
+
G.single_plot(self.state.NUM_LIST, self.state.cos_list[num], file_directory, i)
|
|
728
|
+
|
|
729
|
+
FIO.make_traj_file()
|
|
730
|
+
FIO.argrelextrema_txt_save(self.state.ENERGY_LIST_FOR_PLOTTING, "approx_TS", "max")
|
|
731
|
+
FIO.argrelextrema_txt_save(self.state.ENERGY_LIST_FOR_PLOTTING, "approx_EQ", "min")
|
|
732
|
+
FIO.argrelextrema_txt_save(grad_list, "local_min_grad", "min")
|
|
733
|
+
|
|
734
|
+
self._save_energy_profiles()
|
|
735
|
+
return
|
|
736
|
+
|
|
737
|
+
def _copy_final_results_from_state(self):
|
|
738
|
+
"""Copy final results from the State object to the main Optimize object."""
|
|
739
|
+
if self.state:
|
|
740
|
+
self.final_file_directory = self.state.final_file_directory
|
|
741
|
+
self.final_geometry = self.state.final_geometry
|
|
742
|
+
self.final_energy = self.state.final_energy
|
|
743
|
+
self.final_bias_energy = self.state.final_bias_energy
|
|
744
|
+
self.symmetry = getattr(self.state, 'symmetry', None)
|
|
745
|
+
|
|
746
|
+
# These were not in the original _finalize, but probably should be
|
|
747
|
+
self.bias_pot_params_grad_list = self.state.bias_pot_params_grad_list
|
|
748
|
+
self.bias_pot_params_grad_name_list = self.state.bias_pot_params_grad_name_list
|
|
749
|
+
self.optimized_flag = self.state.optimized_flag
|
|
750
|
+
|
|
751
|
+
def _check_converge_criteria(self, B_g, displacement_vector):
|
|
752
|
+
# (Reads self.config)
|
|
753
|
+
max_force = np.abs(B_g).max()
|
|
754
|
+
max_force_threshold = self.config.MAX_FORCE_THRESHOLD
|
|
755
|
+
|
|
756
|
+
rms_force = self.calculate_rms_safely(B_g)
|
|
757
|
+
rms_force_threshold = self.config.RMS_FORCE_THRESHOLD
|
|
758
|
+
|
|
759
|
+
delta_max_force_threshold = max(0.0, max_force_threshold -1 * max_force)
|
|
760
|
+
delta_rms_force_threshold = max(0.0, rms_force_threshold -1 * rms_force)
|
|
761
|
+
|
|
762
|
+
max_displacement = np.abs(displacement_vector).max()
|
|
763
|
+
max_displacement_threshold = max(self.config.MAX_DISPLACEMENT_THRESHOLD, self.config.MAX_DISPLACEMENT_THRESHOLD + delta_max_force_threshold)
|
|
764
|
+
rms_displacement = self.calculate_rms_safely(displacement_vector)
|
|
765
|
+
rms_displacement_threshold = max(self.config.RMS_DISPLACEMENT_THRESHOLD, self.config.RMS_DISPLACEMENT_THRESHOLD + delta_rms_force_threshold)
|
|
766
|
+
|
|
767
|
+
if max_force < max_force_threshold and rms_force < rms_force_threshold and max_displacement < max_displacement_threshold and rms_displacement < rms_displacement_threshold:
|
|
768
|
+
return True, max_displacement_threshold, rms_displacement_threshold
|
|
769
|
+
return False, max_displacement_threshold, rms_displacement_threshold
|
|
770
|
+
|
|
771
|
+
def _import_calculation_module(self):
|
|
772
|
+
# (Reads self.config)
|
|
773
|
+
xtb_method = None
|
|
774
|
+
if self.config.args.pyscf:
|
|
775
|
+
from multioptpy.Calculator.pyscf_calculation_tools import Calculation
|
|
776
|
+
elif self.config.sqm2:
|
|
777
|
+
from multioptpy.Calculator.sqm2_calculation_tools import Calculation
|
|
778
|
+
print("Use SQM2 potential.")
|
|
779
|
+
elif self.config.sqm1:
|
|
780
|
+
from multioptpy.Calculator.sqm1_calculation_tools import Calculation
|
|
781
|
+
elif self.config.othersoft and self.config.othersoft != "None":
|
|
782
|
+
if self.config.othersoft.lower() == "lj":
|
|
783
|
+
from multioptpy.Calculator.lj_calculation_tools import Calculation
|
|
784
|
+
print("Use Lennard-Jones cluster potential.")
|
|
785
|
+
elif self.config.othersoft.lower() == "emt":
|
|
786
|
+
from multioptpy.Calculator.emt_calculation_tools import Calculation
|
|
787
|
+
print("Use ETM potential.")
|
|
788
|
+
elif self.config.othersoft.lower() == "tersoff":
|
|
789
|
+
from multioptpy.Calculator.tersoff_calculation_tools import Calculation
|
|
790
|
+
print("Use Tersoff potential.")
|
|
791
|
+
else:
|
|
792
|
+
from multioptpy.Calculator.ase_calculation_tools import Calculation
|
|
793
|
+
print("Use", self.config.othersoft)
|
|
794
|
+
with open(self.BPA_FOLDER_DIRECTORY + "use_" + self.config.othersoft + ".txt", "w") as f:
|
|
795
|
+
f.write(self.config.othersoft + "\n")
|
|
796
|
+
f.write(self.config.BASIS_SET + "\n")
|
|
797
|
+
f.write(self.config.FUNCTIONAL + "\n")
|
|
798
|
+
else:
|
|
799
|
+
if self.config.args.usedxtb and self.config.args.usedxtb != "None":
|
|
800
|
+
from multioptpy.Calculator.dxtb_calculation_tools import Calculation
|
|
801
|
+
xtb_method = self.config.args.usedxtb
|
|
802
|
+
elif self.config.args.usextb and self.config.args.usextb != "None":
|
|
803
|
+
from multioptpy.Calculator.tblite_calculation_tools import Calculation
|
|
804
|
+
xtb_method = self.config.args.usextb
|
|
805
|
+
else:
|
|
806
|
+
from multioptpy.Calculator.psi4_calculation_tools import Calculation
|
|
807
|
+
|
|
808
|
+
return Calculation, xtb_method
|
|
809
|
+
|
|
810
|
+
def setup_calculation(self, Calculation):
|
|
811
|
+
# (Reads self.config, self.state)
|
|
812
|
+
# Note: Model_hess is passed from state, but SP is re-created per job.
|
|
813
|
+
# This assumes the initial Model_hess (eye) is what's needed.
|
|
814
|
+
# This might be a flaw if SP needs the *current* Model_hess.
|
|
815
|
+
# Let's assume self.state.Model_hess is correct at time of call.
|
|
816
|
+
|
|
817
|
+
SP = Calculation(
|
|
818
|
+
START_FILE=self.START_FILE,
|
|
819
|
+
N_THREAD=self.config.N_THREAD,
|
|
820
|
+
SET_MEMORY=self.config.SET_MEMORY,
|
|
821
|
+
FUNCTIONAL=self.config.FUNCTIONAL,
|
|
822
|
+
FC_COUNT=self.config.FC_COUNT,
|
|
823
|
+
BPA_FOLDER_DIRECTORY=self.BPA_FOLDER_DIRECTORY,
|
|
824
|
+
Model_hess=self.state.Model_hess, # Reads from state
|
|
825
|
+
software_type=self.config.othersoft,
|
|
826
|
+
unrestrict=self.config.unrestrict,
|
|
827
|
+
SUB_BASIS_SET=self.config.SUB_BASIS_SET,
|
|
828
|
+
BASIS_SET=self.config.BASIS_SET,
|
|
829
|
+
spin_multiplicity=self.config.spin_multiplicity,
|
|
830
|
+
electronic_charge=self.config.electronic_charge,
|
|
831
|
+
excited_state=self.config.excited_state,
|
|
832
|
+
dft_grid=self.config.dft_grid,
|
|
833
|
+
ECP = self.config.ECP,
|
|
834
|
+
software_path_file = self.config.software_path_file
|
|
835
|
+
)
|
|
836
|
+
SP.cpcm_solv_model = self.config.cpcm_solv_model
|
|
837
|
+
SP.alpb_solv_model = self.config.alpb_solv_model
|
|
838
|
+
return SP
|
|
839
|
+
|
|
840
|
+
def write_input_files(self, FIO):
|
|
841
|
+
# (Reads self.config)
|
|
842
|
+
# (This method sets self.element_list and self.state.Model_hess,
|
|
843
|
+
# which is a bit of a side-effect, but we'll keep it)
|
|
844
|
+
|
|
845
|
+
if os.path.splitext(FIO.START_FILE)[1] == ".gjf":
|
|
846
|
+
print("Gaussian input file (.gjf) detected.")
|
|
847
|
+
geometry_list, element_list, electric_charge_and_multiplicity = FIO.read_gjf_file(self.config.electric_charge_and_multiplicity)
|
|
848
|
+
elif os.path.splitext(FIO.START_FILE)[1] == ".inp":
|
|
849
|
+
print("GAMESS/Orca/Q-Chem input file (.inp) detected.")
|
|
850
|
+
geometry_list, element_list, electric_charge_and_multiplicity = FIO.read_gamess_inp_file(self.config.electric_charge_and_multiplicity)
|
|
851
|
+
elif os.path.splitext(FIO.START_FILE)[1] == ".mol":
|
|
852
|
+
print("MDL Molfile (.mol) detected.")
|
|
853
|
+
geometry_list, element_list, electric_charge_and_multiplicity = FIO.read_mol_file(self.config.electric_charge_and_multiplicity)
|
|
854
|
+
elif os.path.splitext(FIO.START_FILE)[1] == ".mol2":
|
|
855
|
+
print("MOL2 file (.mol2) detected.")
|
|
856
|
+
geometry_list, element_list, electric_charge_and_multiplicity = FIO.read_mol2_file(self.config.electric_charge_and_multiplicity)
|
|
857
|
+
else:
|
|
858
|
+
geometry_list, element_list, electric_charge_and_multiplicity = FIO.make_geometry_list(self.config.electric_charge_and_multiplicity)
|
|
859
|
+
|
|
860
|
+
file_directory = FIO.make_psi4_input_file(geometry_list, 0)
|
|
861
|
+
|
|
862
|
+
if self.config.args.pyscf:
|
|
863
|
+
electric_charge_and_multiplicity = self.config.electric_charge_and_multiplicity
|
|
864
|
+
|
|
865
|
+
self.element_list = element_list # Set self.element_list
|
|
866
|
+
# self.Model_hess = np.eye(len(element_list) * 3) # This is now done in OptimizationState
|
|
867
|
+
|
|
868
|
+
return file_directory, electric_charge_and_multiplicity, element_list
|
|
869
|
+
|
|
870
|
+
def save_tmp_energy_profiles(self, iter, e, g, B_g):
|
|
871
|
+
# (This method is pure, no changes needed, writes to files)
|
|
872
|
+
if iter == 0:
|
|
873
|
+
with open(self.BPA_FOLDER_DIRECTORY+"energy_profile.csv","a") as f:
|
|
874
|
+
f.write("energy [hartree] \n")
|
|
875
|
+
with open(self.BPA_FOLDER_DIRECTORY+"energy_profile.csv","a") as f:
|
|
876
|
+
f.write(str(e)+"\n")
|
|
877
|
+
#-------------------gradient profile
|
|
878
|
+
if iter == 0:
|
|
879
|
+
with open(self.BPA_FOLDER_DIRECTORY+"gradient_profile.csv","a") as f:
|
|
880
|
+
f.write("gradient (RMS) [hartree/Bohr] \n")
|
|
881
|
+
with open(self.BPA_FOLDER_DIRECTORY+"gradient_profile.csv","a") as f:
|
|
882
|
+
f.write(str(self.calculate_rms_safely(g))+"\n")
|
|
883
|
+
#-------------------
|
|
884
|
+
if iter == 0:
|
|
885
|
+
with open(self.BPA_FOLDER_DIRECTORY+"bias_gradient_profile.csv","a") as f:
|
|
886
|
+
f.write("bias gradient (RMS) [hartree/Bohr] \n")
|
|
887
|
+
with open(self.BPA_FOLDER_DIRECTORY+"bias_gradient_profile.csv","a") as f:
|
|
888
|
+
f.write(str(self.calculate_rms_safely(B_g))+"\n")
|
|
889
|
+
#-------------------
|
|
890
|
+
return
|
|
891
|
+
|
|
892
|
+
def _save_energy_profiles(self):
|
|
893
|
+
# (Reads self.state)
|
|
894
|
+
with open(self.BPA_FOLDER_DIRECTORY+"energy_profile_kcalmol.csv","w") as f:
|
|
895
|
+
f.write("ITER.,energy[kcal/mol]\n")
|
|
896
|
+
for i in range(len(self.state.ENERGY_LIST_FOR_PLOTTING)):
|
|
897
|
+
f.write(str(i)+","+str(self.state.ENERGY_LIST_FOR_PLOTTING[i] - self.state.ENERGY_LIST_FOR_PLOTTING[0])+"\n")
|
|
898
|
+
return
|
|
899
|
+
|
|
900
|
+
def geom_info_extract(self, force_data, file_directory, B_g, g):
|
|
901
|
+
# (Writes to self.state.cos_list)
|
|
902
|
+
if len(force_data["geom_info"]) > 1:
|
|
903
|
+
CSI = CalculationStructInfo()
|
|
904
|
+
|
|
905
|
+
data_list, data_name_list = CSI.Data_extract(glob.glob(file_directory+"/*.xyz")[0], force_data["geom_info"])
|
|
906
|
+
|
|
907
|
+
for num, i in enumerate(force_data["geom_info"]):
|
|
908
|
+
cos = CSI.calculate_cos(B_g[i-1] - g[i-1], g[i-1])
|
|
909
|
+
self.state.cos_list[num].append(cos)
|
|
910
|
+
|
|
911
|
+
# Need to use self.state.iter to check
|
|
912
|
+
if self.state.iter == 0:
|
|
913
|
+
with open(self.BPA_FOLDER_DIRECTORY+"geometry_info.csv","a") as f:
|
|
914
|
+
f.write(",".join(data_name_list)+"\n")
|
|
915
|
+
|
|
916
|
+
with open(self.BPA_FOLDER_DIRECTORY+"geometry_info.csv","a") as f:
|
|
917
|
+
f.write(",".join(list(map(str,data_list)))+"\n")
|
|
918
|
+
return
|
|
919
|
+
|
|
920
|
+
|
|
921
|
+
def dissociation_check(self, new_geometry, element_list):
|
|
922
|
+
"""
|
|
923
|
+
Checks if the molecular geometry has dissociated into multiple fragments
|
|
924
|
+
based on a distance threshold.
|
|
925
|
+
"""
|
|
926
|
+
# (Reads self.config.DC_check_dist)
|
|
927
|
+
atom_label_list = list(range(len(new_geometry)))
|
|
928
|
+
fragm_atom_num_list = []
|
|
929
|
+
|
|
930
|
+
# 1. Identify all molecular fragments (connected components)
|
|
931
|
+
while len(atom_label_list) > 0:
|
|
932
|
+
tmp_fragm_list = Calculationtools().check_atom_connectivity(new_geometry, element_list, atom_label_list[0])
|
|
933
|
+
atom_label_list = list(set(atom_label_list) - set(tmp_fragm_list))
|
|
934
|
+
fragm_atom_num_list.append(tmp_fragm_list)
|
|
935
|
+
|
|
936
|
+
# 2. Check distances only if there is more than one fragment
|
|
937
|
+
if len(fragm_atom_num_list) > 1:
|
|
938
|
+
fragm_dist_list = []
|
|
939
|
+
|
|
940
|
+
# Ensure geometry is a NumPy array for efficient slicing
|
|
941
|
+
geom_np = np.asarray(new_geometry)
|
|
942
|
+
|
|
943
|
+
# Iterate through all unique pairs of fragments
|
|
944
|
+
for fragm_1_indices, fragm_2_indices in itertools.combinations(fragm_atom_num_list, 2):
|
|
945
|
+
|
|
946
|
+
# Get the coordinates for all atoms in each fragment
|
|
947
|
+
coords1 = geom_np[fragm_1_indices] # Shape (M, 3)
|
|
948
|
+
coords2 = geom_np[fragm_2_indices] # Shape (K, 3)
|
|
949
|
+
|
|
950
|
+
# Reshape coords1 to (M, 1, 3) and coords2 to (1, K, 3)
|
|
951
|
+
# This allows NumPy broadcasting to create all pairs of differences
|
|
952
|
+
# The result (diff_matrix) will have shape (M, K, 3)
|
|
953
|
+
diff_matrix = coords1[:, np.newaxis, :] - coords2[np.newaxis, :, :]
|
|
954
|
+
|
|
955
|
+
# Square the differences and sum along the last axis (axis=2)
|
|
956
|
+
# This calculates the squared Euclidean distance for all pairs
|
|
957
|
+
# The result (sq_dist_matrix) will have shape (M, K)
|
|
958
|
+
sq_dist_matrix = np.sum(diff_matrix**2, axis=2)
|
|
959
|
+
|
|
960
|
+
# Find the minimum value in the squared distance matrix
|
|
961
|
+
min_sq_dist = np.min(sq_dist_matrix)
|
|
962
|
+
|
|
963
|
+
# Take the square root of only the minimum value to get the final distance
|
|
964
|
+
min_dist = np.sqrt(min_sq_dist)
|
|
965
|
+
|
|
966
|
+
fragm_dist_list.append(min_dist)
|
|
967
|
+
|
|
968
|
+
# 3. Check if the closest distance between any two fragments
|
|
969
|
+
# is greater than the dissociation threshold.
|
|
970
|
+
min_interfragment_dist = min(fragm_dist_list)
|
|
971
|
+
|
|
972
|
+
if min_interfragment_dist > self.config.DC_check_dist:
|
|
973
|
+
print(f"Minimum fragment distance (ang.) {min_interfragment_dist:.4f} > {self.config.DC_check_dist}")
|
|
974
|
+
print("These molecules are dissociated.")
|
|
975
|
+
DC_exit_flag = True
|
|
976
|
+
else:
|
|
977
|
+
DC_exit_flag = False
|
|
978
|
+
else:
|
|
979
|
+
# Only one fragment, so it's not dissociated
|
|
980
|
+
DC_exit_flag = False
|
|
981
|
+
|
|
982
|
+
return DC_exit_flag
|
|
983
|
+
|
|
984
|
+
def calculate_rms_safely(self, vector, threshold=1e-10):
|
|
985
|
+
# (This method is pure, no changes needed)
|
|
986
|
+
filtered_vector = vector[np.abs(vector) > threshold]
|
|
987
|
+
if filtered_vector.size > 0:
|
|
988
|
+
return np.sqrt((filtered_vector**2).mean())
|
|
989
|
+
else:
|
|
990
|
+
return 0.0
|
|
991
|
+
|
|
992
|
+
def print_info(self, e, B_e, B_g, displacement_vector, pre_e, pre_B_e, max_displacement_threshold, rms_displacement_threshold):
|
|
993
|
+
# (Reads self.config)
|
|
994
|
+
rms_force = self.calculate_rms_safely(np.abs(B_g))
|
|
995
|
+
rms_displacement = self.calculate_rms_safely(np.abs(displacement_vector))
|
|
996
|
+
max_B_g = np.abs(B_g).max()
|
|
997
|
+
max_displacement = np.abs(displacement_vector).max()
|
|
998
|
+
print("caluculation results (unit a.u.):")
|
|
999
|
+
print(" Value Threshold ")
|
|
1000
|
+
print("ENERGY : {:>15.12f} ".format(e))
|
|
1001
|
+
print("BIAS ENERGY : {:>15.12f} ".format(B_e))
|
|
1002
|
+
print("Maximum Force : {0:>15.12f} {1:>15.12f} ".format(max_B_g, self.config.MAX_FORCE_THRESHOLD))
|
|
1003
|
+
print("RMS Force : {0:>15.12f} {1:>15.12f} ".format(rms_force, self.config.RMS_FORCE_THRESHOLD))
|
|
1004
|
+
print("Maximum Displacement : {0:>15.12f} {1:>15.12f} ".format(max_displacement, max_displacement_threshold))
|
|
1005
|
+
print("RMS Displacement : {0:>15.12f} {1:>15.12f} ".format(rms_displacement, rms_displacement_threshold))
|
|
1006
|
+
print("ENERGY SHIFT : {:>15.12f} ".format(e - pre_e))
|
|
1007
|
+
print("BIAS ENERGY SHIFT : {:>15.12f} ".format(B_e - pre_B_e))
|
|
1008
|
+
return
|
|
1009
|
+
|
|
1010
|
+
def calc_fragement_grads(self, gradient, fragment_list):
|
|
1011
|
+
# (This method is pure, no changes needed)
|
|
1012
|
+
calced_gradient = gradient
|
|
1013
|
+
for fragment in fragment_list:
|
|
1014
|
+
tmp_grad = np.array([0.0, 0.0, 0.0], dtype="float64")
|
|
1015
|
+
for atom_num in fragment:
|
|
1016
|
+
tmp_grad += gradient[atom_num-1]
|
|
1017
|
+
tmp_grad /= len(fragment)
|
|
1018
|
+
|
|
1019
|
+
for atom_num in fragment:
|
|
1020
|
+
calced_gradient[atom_num-1] = copy.deepcopy(tmp_grad)
|
|
1021
|
+
return calced_gradient
|
|
1022
|
+
|
|
1023
|
+
def optimize_oniom(self):
|
|
1024
|
+
"""
|
|
1025
|
+
Perform ONIOM optimization using a high-level QM method for a subset of atoms
|
|
1026
|
+
and a low-level method for the entire system.
|
|
1027
|
+
|
|
1028
|
+
Refactored to use self.config and self.state.
|
|
1029
|
+
"""
|
|
1030
|
+
# 1. Parse input parameters and initialize file IO
|
|
1031
|
+
force_data = force_data_parser(self.config.args)
|
|
1032
|
+
high_layer_atom_num = force_data["oniom_flag"][0]
|
|
1033
|
+
link_atom_num = force_data["oniom_flag"][1]
|
|
1034
|
+
calc_method = force_data["oniom_flag"][2]
|
|
1035
|
+
|
|
1036
|
+
FIO = FileIO(self.BPA_FOLDER_DIRECTORY, self.START_FILE)
|
|
1037
|
+
|
|
1038
|
+
# 2. Write input files and create the State object
|
|
1039
|
+
geometry_list, element_list, electric_charge_and_multiplicity = self.write_input_files(FIO)
|
|
1040
|
+
self.element_list = element_list # Set on self for helpers
|
|
1041
|
+
|
|
1042
|
+
# Create the main State object for the "Real" system
|
|
1043
|
+
self.state = OptimizationState(element_list)
|
|
1044
|
+
self.state.cos_list = [[] for i in range(len(force_data["geom_info"]))]
|
|
1045
|
+
|
|
1046
|
+
file_directory = FIO.make_psi4_input_file(geometry_list, 0)
|
|
1047
|
+
|
|
1048
|
+
# 3. Import appropriate calculation modules
|
|
1049
|
+
if self.config.args.pyscf:
|
|
1050
|
+
from multioptpy.Calculator.pyscf_calculation_tools import Calculation as HL_Calculation
|
|
1051
|
+
else:
|
|
1052
|
+
from multioptpy.Calculator.psi4_calculation_tools import Calculation as HL_Calculation
|
|
1053
|
+
|
|
1054
|
+
if calc_method in ["GFN2-xTB", "GFN1-xTB", "IPEA1-xTB"]:
|
|
1055
|
+
from multioptpy.Calculator.tblite_calculation_tools import Calculation as LL_Calculation
|
|
1056
|
+
else:
|
|
1057
|
+
from multioptpy.Calculator.ase_calculation_tools import Calculation as LL_Calculation
|
|
1058
|
+
|
|
1059
|
+
# Save ONIOM configuration to file
|
|
1060
|
+
with open(self.BPA_FOLDER_DIRECTORY+"ONIOM2.txt", "w") as f:
|
|
1061
|
+
f.write("### Low layer ###\n")
|
|
1062
|
+
f.write(calc_method+"\n")
|
|
1063
|
+
f.write("### High layer ###\n")
|
|
1064
|
+
f.write(self.config.BASIS_SET+"\n")
|
|
1065
|
+
f.write(self.config.FUNCTIONAL+"\n")
|
|
1066
|
+
|
|
1067
|
+
# 4. Initialize geometries and ONIOM setup
|
|
1068
|
+
geom_num_list = []
|
|
1069
|
+
for i in range(2, len(geometry_list[0])):
|
|
1070
|
+
geom_num_list.append(geometry_list[0][i][1:4])
|
|
1071
|
+
geom_num_list = np.array(geom_num_list, dtype="float64") / self.config.bohr2angstroms
|
|
1072
|
+
self.state.geom_num_list = geom_num_list # Set initial geometry in state
|
|
1073
|
+
|
|
1074
|
+
linker_atom_pair_num = specify_link_atom_pairs(geom_num_list, element_list, high_layer_atom_num, link_atom_num)
|
|
1075
|
+
print("Boundary of high layer and low layer:", linker_atom_pair_num)
|
|
1076
|
+
|
|
1077
|
+
high_layer_geom_num_list, high_layer_element_list = separate_high_layer_and_low_layer(
|
|
1078
|
+
geom_num_list, linker_atom_pair_num, high_layer_atom_num, element_list)
|
|
1079
|
+
|
|
1080
|
+
real_2_highlayer_label_connect_dict, highlayer_2_real_label_connect_dict = link_number_high_layer_and_low_layer(high_layer_atom_num)
|
|
1081
|
+
|
|
1082
|
+
# 5. Initialize model Hessians (local state for ONIOM)
|
|
1083
|
+
LL_Model_hess = np.eye(len(element_list)*3)
|
|
1084
|
+
HL_Model_hess = np.eye((len(high_layer_element_list))*3)
|
|
1085
|
+
|
|
1086
|
+
# Create mask for high layer atoms
|
|
1087
|
+
bool_list = []
|
|
1088
|
+
for i in range(len(element_list)):
|
|
1089
|
+
if i in high_layer_atom_num:
|
|
1090
|
+
bool_list.extend([True, True, True])
|
|
1091
|
+
else:
|
|
1092
|
+
bool_list.extend([False, False, False])
|
|
1093
|
+
|
|
1094
|
+
# 6. Initialize bias potential calculators
|
|
1095
|
+
# (self.CalcBiaspot will be used for the "Real" system bias)
|
|
1096
|
+
LL_Calc_BiasPot = BiasPotentialCalculation(self.BPA_FOLDER_DIRECTORY)
|
|
1097
|
+
# HL_Calc_BiasPot = BiasPotentialCalculation(self.BPA_FOLDER_DIRECTORY) # Seems unused in original
|
|
1098
|
+
self.CalcBiaspot = BiasPotentialCalculation(self.BPA_FOLDER_DIRECTORY) # For main state
|
|
1099
|
+
|
|
1100
|
+
with open(self.BPA_FOLDER_DIRECTORY+"input.txt", "w") as f:
|
|
1101
|
+
f.write(str(vars(self.config.args)))
|
|
1102
|
+
|
|
1103
|
+
# 7. Initialize ONIOM-specific previous-step variables (as local vars)
|
|
1104
|
+
pre_model_HL_B_e = 0.0
|
|
1105
|
+
pre_model_HL_B_g = np.zeros((len(high_layer_element_list), 3))
|
|
1106
|
+
pre_model_HL_g = np.zeros((len(high_layer_element_list), 3))
|
|
1107
|
+
# pre_model_LL_B_g = np.zeros((len(high_layer_element_list), 3)) # Seems unused
|
|
1108
|
+
pre_real_LL_B_e = 0.0
|
|
1109
|
+
pre_real_LL_e = 0.0
|
|
1110
|
+
pre_real_LL_B_g = np.zeros((len(element_list), 3))
|
|
1111
|
+
pre_real_LL_g = np.zeros((len(element_list), 3))
|
|
1112
|
+
pre_real_LL_move_vector = np.zeros((len(element_list), 3))
|
|
1113
|
+
pre_model_HL_move_vector = np.zeros((len(high_layer_element_list), 3))
|
|
1114
|
+
|
|
1115
|
+
# 8. Initialize HL optimizer
|
|
1116
|
+
HL_CMV = CalculateMoveVector(self.config.DELTA, high_layer_element_list[:len(high_layer_atom_num)],
|
|
1117
|
+
self.config.args.saddle_order, self.config.FC_COUNT, self.config.temperature,
|
|
1118
|
+
max_trust_radius=self.config.max_trust_radius, min_trust_radius=self.config.min_trust_radius)
|
|
1119
|
+
HL_optimizer_instances = HL_CMV.initialization(force_data["opt_method"])
|
|
1120
|
+
|
|
1121
|
+
for i in range(len(HL_optimizer_instances)):
|
|
1122
|
+
HL_optimizer_instances[i].set_hessian(HL_Model_hess[:len(high_layer_atom_num)*3, :len(high_layer_atom_num)*3])
|
|
1123
|
+
if self.config.DELTA != "x":
|
|
1124
|
+
HL_optimizer_instances[i].DELTA = self.config.DELTA
|
|
1125
|
+
|
|
1126
|
+
# 9. Initialize calculation instances
|
|
1127
|
+
HLSP = HL_Calculation(START_FILE=self.START_FILE,
|
|
1128
|
+
SUB_BASIS_SET=self.config.SUB_BASIS_SET,
|
|
1129
|
+
BASIS_SET=self.config.BASIS_SET,
|
|
1130
|
+
N_THREAD=self.config.N_THREAD,
|
|
1131
|
+
SET_MEMORY=self.config.SET_MEMORY,
|
|
1132
|
+
FUNCTIONAL=self.config.FUNCTIONAL,
|
|
1133
|
+
FC_COUNT=self.config.FC_COUNT,
|
|
1134
|
+
BPA_FOLDER_DIRECTORY=self.BPA_FOLDER_DIRECTORY,
|
|
1135
|
+
Model_hess=HL_Model_hess[:len(high_layer_atom_num)*3, :len(high_layer_atom_num)*3],
|
|
1136
|
+
unrestrict=self.config.unrestrict,
|
|
1137
|
+
excited_state=self.config.excited_state,
|
|
1138
|
+
electronic_charge=self.config.electronic_charge,
|
|
1139
|
+
spin_multiplicity=self.config.spin_multiplicity
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1142
|
+
LLSP = LL_Calculation(START_FILE=self.START_FILE,
|
|
1143
|
+
SUB_BASIS_SET=self.config.SUB_BASIS_SET,
|
|
1144
|
+
BASIS_SET=self.config.BASIS_SET,
|
|
1145
|
+
N_THREAD=self.config.N_THREAD,
|
|
1146
|
+
SET_MEMORY=self.config.SET_MEMORY,
|
|
1147
|
+
FUNCTIONAL=self.config.FUNCTIONAL,
|
|
1148
|
+
FC_COUNT=self.config.FC_COUNT,
|
|
1149
|
+
BPA_FOLDER_DIRECTORY=self.BPA_FOLDER_DIRECTORY,
|
|
1150
|
+
Model_hess=LL_Model_hess,
|
|
1151
|
+
unrestrict=self.config.unrestrict,
|
|
1152
|
+
software_type=calc_method,
|
|
1153
|
+
excited_state=self.config.excited_state)
|
|
1154
|
+
|
|
1155
|
+
# 10. Initialize result tracking (uses self.state)
|
|
1156
|
+
real_grad_list = []
|
|
1157
|
+
real_bias_grad_list = []
|
|
1158
|
+
|
|
1159
|
+
# 11. Main optimization loop
|
|
1160
|
+
for iter in range(self.config.NSTEP):
|
|
1161
|
+
self.state.iter = iter
|
|
1162
|
+
|
|
1163
|
+
exit_file_detect = os.path.exists(self.BPA_FOLDER_DIRECTORY+"end.txt")
|
|
1164
|
+
if exit_file_detect:
|
|
1165
|
+
self.state.exit_flag = True
|
|
1166
|
+
break
|
|
1167
|
+
|
|
1168
|
+
print(f"\n# ITR. {iter}\n")
|
|
1169
|
+
|
|
1170
|
+
if iter == 0:
|
|
1171
|
+
high_layer_initial_geom_num_list = high_layer_geom_num_list.copy() # Bohr
|
|
1172
|
+
high_layer_pre_geom = high_layer_initial_geom_num_list.copy() # Bohr
|
|
1173
|
+
real_initial_geom_num_list = geom_num_list.copy() # Bohr
|
|
1174
|
+
real_pre_geom = real_initial_geom_num_list.copy() # Bohr
|
|
1175
|
+
|
|
1176
|
+
# --- Model Low Layer Calc ---
|
|
1177
|
+
print("Model low layer calculation")
|
|
1178
|
+
model_LL_e, model_LL_g, high_layer_geom_num_list, finish_frag = LLSP.single_point(
|
|
1179
|
+
file_directory, high_layer_element_list, iter, electric_charge_and_multiplicity,
|
|
1180
|
+
calc_method, geom_num_list=high_layer_geom_num_list*self.config.bohr2angstroms)
|
|
1181
|
+
|
|
1182
|
+
if finish_frag:
|
|
1183
|
+
self.state.exit_flag = True
|
|
1184
|
+
break
|
|
1185
|
+
|
|
1186
|
+
# --- Microiterations ---
|
|
1187
|
+
print("Processing microiteration...")
|
|
1188
|
+
LL_CMV = CalculateMoveVector(self.config.DELTA, element_list, self.config.args.saddle_order, self.config.FC_COUNT, self.config.temperature)
|
|
1189
|
+
LL_optimizer_instances = LL_CMV.initialization(["fire"])
|
|
1190
|
+
LL_optimizer_instances[0].display_flag = False
|
|
1191
|
+
|
|
1192
|
+
low_layer_converged = False
|
|
1193
|
+
|
|
1194
|
+
# Use geom_num_list from main state
|
|
1195
|
+
current_geom_num_list = self.state.geom_num_list.copy()
|
|
1196
|
+
|
|
1197
|
+
for microiter in range(self.config.microiter_num):
|
|
1198
|
+
LLSP.Model_hess = LL_Model_hess
|
|
1199
|
+
|
|
1200
|
+
real_LL_e, real_LL_g, current_geom_num_list, finish_frag = LLSP.single_point(
|
|
1201
|
+
file_directory, element_list, microiter, electric_charge_and_multiplicity,
|
|
1202
|
+
calc_method, geom_num_list=current_geom_num_list*self.config.bohr2angstroms)
|
|
1203
|
+
|
|
1204
|
+
LL_Model_hess = LLSP.Model_hess
|
|
1205
|
+
|
|
1206
|
+
LL_Calc_BiasPot.Model_hess = LL_Model_hess
|
|
1207
|
+
_, real_LL_B_e, real_LL_B_g, LL_BPA_hessian = LL_Calc_BiasPot.main(
|
|
1208
|
+
real_LL_e, real_LL_g, current_geom_num_list, element_list,
|
|
1209
|
+
force_data, pre_real_LL_B_g, microiter, real_initial_geom_num_list)
|
|
1210
|
+
|
|
1211
|
+
for x in range(len(LL_optimizer_instances)):
|
|
1212
|
+
LL_optimizer_instances[x].set_bias_hessian(LL_BPA_hessian)
|
|
1213
|
+
if microiter % self.config.FC_COUNT == 0: # Using FC_COUNT, not mFC_COUNT
|
|
1214
|
+
LL_optimizer_instances[x].set_hessian(LL_Model_hess)
|
|
1215
|
+
|
|
1216
|
+
if len(force_data["opt_fragment"]) > 0:
|
|
1217
|
+
real_LL_B_g = copy.deepcopy(self.calc_fragement_grads(real_LL_B_g, force_data["opt_fragment"]))
|
|
1218
|
+
real_LL_g = copy.deepcopy(self.calc_fragement_grads(real_LL_g, force_data["opt_fragment"]))
|
|
1219
|
+
|
|
1220
|
+
prev_geom = current_geom_num_list.copy()
|
|
1221
|
+
|
|
1222
|
+
current_geom_num_list_ang, LL_move_vector, LL_optimizer_instances = LL_CMV.calc_move_vector(
|
|
1223
|
+
microiter, current_geom_num_list, real_LL_B_g, pre_real_LL_B_g,
|
|
1224
|
+
real_pre_geom, real_LL_B_e, pre_real_LL_B_e,
|
|
1225
|
+
pre_real_LL_move_vector, real_initial_geom_num_list,
|
|
1226
|
+
real_LL_g, pre_real_LL_g, LL_optimizer_instances, print_flag=False)
|
|
1227
|
+
|
|
1228
|
+
current_geom_num_list = current_geom_num_list_ang / self.config.bohr2angstroms
|
|
1229
|
+
|
|
1230
|
+
# Fix high layer atoms
|
|
1231
|
+
for key, value in highlayer_2_real_label_connect_dict.items():
|
|
1232
|
+
current_geom_num_list[value-1] = copy.deepcopy(high_layer_geom_num_list[key-1]) # Already in Bohr
|
|
1233
|
+
|
|
1234
|
+
# Fix user-specified atoms
|
|
1235
|
+
if len(force_data["fix_atoms"]) > 0:
|
|
1236
|
+
for j in force_data["fix_atoms"]:
|
|
1237
|
+
current_geom_num_list[j-1] = copy.deepcopy(real_initial_geom_num_list[j-1]) # Already in Bohr
|
|
1238
|
+
|
|
1239
|
+
displacement_vector = current_geom_num_list - prev_geom
|
|
1240
|
+
|
|
1241
|
+
# Calculate convergence metrics for low layer atoms only
|
|
1242
|
+
low_layer_grads = []
|
|
1243
|
+
low_layer_displacements = []
|
|
1244
|
+
for i in range(len(element_list)):
|
|
1245
|
+
if (i+1) not in high_layer_atom_num:
|
|
1246
|
+
low_layer_grads.append(real_LL_B_g[i])
|
|
1247
|
+
low_layer_displacements.append(displacement_vector[i])
|
|
1248
|
+
|
|
1249
|
+
low_layer_grads = np.array(low_layer_grads)
|
|
1250
|
+
low_layer_displacements = np.array(low_layer_displacements)
|
|
1251
|
+
|
|
1252
|
+
low_layer_rms_grad = self.calculate_rms_safely(low_layer_grads)
|
|
1253
|
+
max_displacement = np.abs(displacement_vector).max() if len(displacement_vector) > 0 else 0
|
|
1254
|
+
rms_displacement = self.calculate_rms_safely(displacement_vector)
|
|
1255
|
+
energy_shift = -1 * pre_real_LL_B_e + real_LL_B_e
|
|
1256
|
+
|
|
1257
|
+
if microiter % 10 == 0:
|
|
1258
|
+
print(f"M. ITR. {microiter}")
|
|
1259
|
+
print("Microiteration results:")
|
|
1260
|
+
print(f"LOW LAYER BIAS ENERGY : {float(real_LL_B_e):10.8f}")
|
|
1261
|
+
print(f"LOW LAYER ENERGY : {float(real_LL_e):10.8f}")
|
|
1262
|
+
print(f"LOW LAYER MAX GRADIENT: {float(low_layer_grads.max() if len(low_layer_grads) > 0 else 0):10.8f}")
|
|
1263
|
+
print(f"LOW LAYER RMS GRADIENT: {float(low_layer_rms_grad):10.8f}")
|
|
1264
|
+
print(f"MAX DISPLACEMENT : {float(max_displacement):10.8f}")
|
|
1265
|
+
print(f"RMS DISPLACEMENT : {float(rms_displacement):10.8f}")
|
|
1266
|
+
print(f"ENERGY SHIFT : {float(energy_shift):10.8f}")
|
|
1267
|
+
|
|
1268
|
+
# Check convergence (using hardcoded values from original)
|
|
1269
|
+
if (low_layer_rms_grad < 0.0003) and \
|
|
1270
|
+
(low_layer_grads.max() < 0.0006 if len(low_layer_grads) > 0 else True) and \
|
|
1271
|
+
(max_displacement < 0.003) and \
|
|
1272
|
+
(rms_displacement < 0.002):
|
|
1273
|
+
print("Low layer converged... (microiteration)")
|
|
1274
|
+
low_layer_converged = True
|
|
1275
|
+
break
|
|
1276
|
+
|
|
1277
|
+
# Update previous values for next microiteration
|
|
1278
|
+
pre_real_LL_B_e = real_LL_B_e
|
|
1279
|
+
pre_real_LL_g = real_LL_g
|
|
1280
|
+
pre_real_LL_B_g = real_LL_B_g
|
|
1281
|
+
pre_real_LL_move_vector = LL_move_vector
|
|
1282
|
+
|
|
1283
|
+
# End of microiteration loop
|
|
1284
|
+
if not low_layer_converged:
|
|
1285
|
+
print("Reached maximum number of microiterations.")
|
|
1286
|
+
print("Microiteration complete.")
|
|
1287
|
+
|
|
1288
|
+
# Update the main geometry state
|
|
1289
|
+
self.state.geom_num_list = current_geom_num_list
|
|
1290
|
+
geom_num_list = current_geom_num_list # Use for this iter
|
|
1291
|
+
|
|
1292
|
+
# --- Model High Layer Calc ---
|
|
1293
|
+
print("Model system (high layer)")
|
|
1294
|
+
HLSP.Model_hess = HL_Model_hess
|
|
1295
|
+
model_HL_e, model_HL_g, high_layer_geom_num_list, finish_frag = HLSP.single_point(
|
|
1296
|
+
file_directory, high_layer_element_list, iter, electric_charge_and_multiplicity,
|
|
1297
|
+
method="", geom_num_list=high_layer_geom_num_list*self.config.bohr2angstroms)
|
|
1298
|
+
|
|
1299
|
+
HL_Model_hess = HLSP.Model_hess
|
|
1300
|
+
|
|
1301
|
+
if finish_frag:
|
|
1302
|
+
self.state.exit_flag = True
|
|
1303
|
+
break
|
|
1304
|
+
|
|
1305
|
+
# --- Combine Gradients ---
|
|
1306
|
+
# Use LL_Calc_BiasPot to get bias gradient on "Real" system
|
|
1307
|
+
_, tmp_model_HL_B_e, tmp_model_HL_B_g, LL_BPA_hessian = LL_Calc_BiasPot.main(
|
|
1308
|
+
0.0, real_LL_g*0.0, geom_num_list, element_list, force_data, pre_real_LL_B_g*0.0, iter, real_initial_geom_num_list)
|
|
1309
|
+
|
|
1310
|
+
tmp_model_HL_g = tmp_model_HL_B_g * 0.0
|
|
1311
|
+
|
|
1312
|
+
for key, value in real_2_highlayer_label_connect_dict.items():
|
|
1313
|
+
tmp_model_HL_B_g[key-1] += model_HL_g[value-1] - model_LL_g[value-1]
|
|
1314
|
+
tmp_model_HL_g[key-1] += model_HL_g[value-1] - model_LL_g[value-1]
|
|
1315
|
+
|
|
1316
|
+
HL_BPA_hessian = LL_BPA_hessian[np.ix_(bool_list, bool_list)]
|
|
1317
|
+
|
|
1318
|
+
for i in range(len(HL_optimizer_instances)):
|
|
1319
|
+
HL_optimizer_instances[i].set_bias_hessian(HL_BPA_hessian)
|
|
1320
|
+
if iter % self.config.FC_COUNT == 0:
|
|
1321
|
+
HL_optimizer_instances[i].set_hessian(HL_Model_hess[:len(high_layer_atom_num)*3, :len(high_layer_atom_num)*3])
|
|
1322
|
+
|
|
1323
|
+
if len(force_data["opt_fragment"]) > 0:
|
|
1324
|
+
tmp_model_HL_B_g = copy.deepcopy(self.calc_fragement_grads(tmp_model_HL_B_g, force_data["opt_fragment"]))
|
|
1325
|
+
tmp_model_HL_g = copy.deepcopy(self.calc_fragement_grads(tmp_model_HL_g, force_data["opt_fragment"]))
|
|
1326
|
+
|
|
1327
|
+
model_HL_B_g = copy.deepcopy(model_HL_g)
|
|
1328
|
+
model_HL_B_e = model_HL_e + tmp_model_HL_B_e
|
|
1329
|
+
|
|
1330
|
+
for key, value in real_2_highlayer_label_connect_dict.items():
|
|
1331
|
+
model_HL_B_g[value-1] += tmp_model_HL_B_g[key-1] # This seems incorrect logic, but mirrors original
|
|
1332
|
+
|
|
1333
|
+
pre_high_layer_geom_num_list = high_layer_geom_num_list
|
|
1334
|
+
|
|
1335
|
+
# --- Calculate HL Move Vector ---
|
|
1336
|
+
high_layer_geom_num_list_ang, move_vector, HL_optimizer_instances = HL_CMV.calc_move_vector(
|
|
1337
|
+
iter, high_layer_geom_num_list[:len(high_layer_atom_num)], model_HL_B_g[:len(high_layer_atom_num)], pre_model_HL_B_g[:len(high_layer_atom_num)],
|
|
1338
|
+
pre_high_layer_geom_num_list[:len(high_layer_atom_num)], model_HL_B_e, pre_model_HL_B_e,
|
|
1339
|
+
pre_model_HL_move_vector[:len(high_layer_atom_num)], high_layer_pre_geom[:len(high_layer_atom_num)],
|
|
1340
|
+
model_HL_g[:len(high_layer_atom_num)], pre_model_HL_g[:len(high_layer_atom_num)], HL_optimizer_instances)
|
|
1341
|
+
|
|
1342
|
+
high_layer_geom_num_list = high_layer_geom_num_list_ang / self.config.bohr2angstroms
|
|
1343
|
+
|
|
1344
|
+
# --- Update Full System Geometry ---
|
|
1345
|
+
for l in range(len(high_layer_geom_num_list) - len(linker_atom_pair_num)):
|
|
1346
|
+
geom_num_list[highlayer_2_real_label_connect_dict[l+1]-1] = copy.deepcopy(high_layer_geom_num_list[l])
|
|
1347
|
+
|
|
1348
|
+
# Project out translation and rotation
|
|
1349
|
+
geom_num_list -= Calculationtools().calc_center_of_mass(geom_num_list, element_list)
|
|
1350
|
+
geom_num_list, _ = Calculationtools().kabsch_algorithm(geom_num_list, real_pre_geom)
|
|
1351
|
+
|
|
1352
|
+
# Update high layer geometry after alignment
|
|
1353
|
+
high_layer_geom_num_list, high_layer_element_list = separate_high_layer_and_low_layer(
|
|
1354
|
+
geom_num_list, linker_atom_pair_num, high_layer_atom_num, element_list)
|
|
1355
|
+
|
|
1356
|
+
# --- Combine Energies and Gradients for REAL system ---
|
|
1357
|
+
real_e = real_LL_e + model_HL_e - model_LL_e
|
|
1358
|
+
real_B_e = real_LL_B_e + model_HL_B_e - model_LL_e # Original uses model_LL_e, not B_e
|
|
1359
|
+
real_g = real_LL_g + tmp_model_HL_g
|
|
1360
|
+
real_B_g = real_LL_B_g + tmp_model_HL_g
|
|
1361
|
+
|
|
1362
|
+
# --- Update Main State ---
|
|
1363
|
+
self.state.e = real_e
|
|
1364
|
+
self.state.B_e = real_B_e
|
|
1365
|
+
self.state.g = real_g
|
|
1366
|
+
self.state.B_g = real_B_g
|
|
1367
|
+
self.state.geom_num_list = geom_num_list
|
|
1368
|
+
|
|
1369
|
+
self.save_tmp_energy_profiles(iter, real_e, real_g, real_B_g)
|
|
1370
|
+
self.state.ENERGY_LIST_FOR_PLOTTING.append(real_e*self.config.hartree2kcalmol)
|
|
1371
|
+
self.state.BIAS_ENERGY_LIST_FOR_PLOTTING.append(real_B_e*self.config.hartree2kcalmol)
|
|
1372
|
+
self.state.NUM_LIST.append(iter)
|
|
1373
|
+
|
|
1374
|
+
self.geom_info_extract(force_data, file_directory, real_B_g, real_g)
|
|
1375
|
+
|
|
1376
|
+
if len(linker_atom_pair_num) > 0:
|
|
1377
|
+
tmp_real_B_g = model_HL_B_g[:-len(linker_atom_pair_num)].reshape(-1,1)
|
|
1378
|
+
else:
|
|
1379
|
+
tmp_real_B_g = model_HL_B_g.reshape(-1,1)
|
|
1380
|
+
|
|
1381
|
+
abjusted_high_layer_geom_num_list, _ = Calculationtools().kabsch_algorithm(high_layer_geom_num_list, pre_high_layer_geom_num_list)
|
|
1382
|
+
|
|
1383
|
+
if len(linker_atom_pair_num) > 0:
|
|
1384
|
+
tmp_displacement_vector = (abjusted_high_layer_geom_num_list - pre_high_layer_geom_num_list)[:-len(linker_atom_pair_num)].reshape(-1,1)
|
|
1385
|
+
else:
|
|
1386
|
+
tmp_displacement_vector = (abjusted_high_layer_geom_num_list - pre_high_layer_geom_num_list).reshape(-1,1)
|
|
1387
|
+
|
|
1388
|
+
# --- Check Convergence (on HL model) ---
|
|
1389
|
+
converge_flag, max_displacement_threshold, rms_displacement_threshold = self._check_converge_criteria(tmp_real_B_g, tmp_displacement_vector)
|
|
1390
|
+
|
|
1391
|
+
self.print_info(real_e, real_B_e, tmp_real_B_g, tmp_displacement_vector, self.state.pre_e, self.state.pre_B_e,
|
|
1392
|
+
max_displacement_threshold, rms_displacement_threshold)
|
|
1393
|
+
|
|
1394
|
+
real_grad_list.append(self.calculate_rms_safely(real_g))
|
|
1395
|
+
real_bias_grad_list.append(self.calculate_rms_safely(real_B_g))
|
|
1396
|
+
|
|
1397
|
+
# Update state lists
|
|
1398
|
+
self.state.grad_list = real_grad_list
|
|
1399
|
+
self.state.bias_grad_list = real_bias_grad_list
|
|
1400
|
+
|
|
1401
|
+
if converge_flag:
|
|
1402
|
+
self.state.optimized_flag = True
|
|
1403
|
+
print("\n=====================================================")
|
|
1404
|
+
print("converged!!!")
|
|
1405
|
+
print("=====================================================")
|
|
1406
|
+
break
|
|
1407
|
+
|
|
1408
|
+
# Fix user-specified atoms
|
|
1409
|
+
if len(force_data["fix_atoms"]) > 0:
|
|
1410
|
+
for j in force_data["fix_atoms"]:
|
|
1411
|
+
geom_num_list[j-1] = copy.deepcopy(real_initial_geom_num_list[j-1]) # Bohr
|
|
1412
|
+
|
|
1413
|
+
DC_exit_flag = self.dissociation_check(geom_num_list, element_list)
|
|
1414
|
+
if DC_exit_flag:
|
|
1415
|
+
self.state.DC_check_flag = True
|
|
1416
|
+
break
|
|
1417
|
+
|
|
1418
|
+
# --- Update Previous State Variables ---
|
|
1419
|
+
self.state.pre_B_e = real_B_e
|
|
1420
|
+
self.state.pre_e = real_e
|
|
1421
|
+
self.state.pre_geom = geom_num_list
|
|
1422
|
+
real_pre_geom = geom_num_list # Update local var
|
|
1423
|
+
|
|
1424
|
+
pre_model_HL_B_g = model_HL_B_g
|
|
1425
|
+
pre_model_HL_g = model_HL_g
|
|
1426
|
+
pre_model_HL_B_e = model_HL_B_e
|
|
1427
|
+
pre_model_HL_move_vector = move_vector
|
|
1428
|
+
|
|
1429
|
+
# Create input for next iteration
|
|
1430
|
+
geometry_list = FIO.print_geometry_list(geom_num_list*self.config.bohr2angstroms, element_list, electric_charge_and_multiplicity)
|
|
1431
|
+
file_directory = FIO.make_psi4_input_file(geometry_list, iter+1)
|
|
1432
|
+
|
|
1433
|
+
else: # Loop finished without break
|
|
1434
|
+
self.state.optimized_flag = False
|
|
1435
|
+
print("Reached maximum number of iterations. This is not converged.")
|
|
1436
|
+
with open(self.BPA_FOLDER_DIRECTORY+"not_converged.txt", "w") as f:
|
|
1437
|
+
f.write("Reached maximum number of iterations. This is not converged.")
|
|
1438
|
+
|
|
1439
|
+
if self.state.DC_check_flag:
|
|
1440
|
+
with open(self.BPA_FOLDER_DIRECTORY+"dissociation_is_detected.txt", "w") as f:
|
|
1441
|
+
f.write("These molecules are dissociated.")
|
|
1442
|
+
|
|
1443
|
+
# --- 12. Finalize and Save Results ---
|
|
1444
|
+
G = Graph(self.BPA_FOLDER_DIRECTORY)
|
|
1445
|
+
|
|
1446
|
+
# Finalize plots and save results (using self.state lists)
|
|
1447
|
+
self._finalize_optimization(FIO, G, self.state.grad_list, self.state.bias_grad_list, file_directory, force_data, geom_num_list, e, B_e, SP, self.state.exit_flag) # Pass LLSP as dummy
|
|
1448
|
+
|
|
1449
|
+
# Copy final results from state to self
|
|
1450
|
+
self._copy_final_results_from_state()
|
|
1451
|
+
return
|
|
1452
|
+
|
|
1453
|
+
def get_result_file_path(self):
|
|
1454
|
+
"""
|
|
1455
|
+
Sets the absolute file paths for optimization results.
|
|
1456
|
+
Relies on self.BPA_FOLDER_DIRECTORY and self.START_FILE
|
|
1457
|
+
which are set by _make_init_directory().
|
|
1458
|
+
"""
|
|
1459
|
+
try:
|
|
1460
|
+
if (hasattr(self, 'BPA_FOLDER_DIRECTORY') and self.BPA_FOLDER_DIRECTORY and
|
|
1461
|
+
hasattr(self, 'START_FILE') and self.START_FILE):
|
|
1462
|
+
|
|
1463
|
+
base_name = os.path.splitext(os.path.basename(self.START_FILE))[0]
|
|
1464
|
+
optimized_filename = f"{base_name}_optimized.xyz"
|
|
1465
|
+
traj_filename = f"{base_name}_traj.xyz"
|
|
1466
|
+
|
|
1467
|
+
self.optimized_struct_file = os.path.abspath(os.path.join(self.BPA_FOLDER_DIRECTORY, optimized_filename))
|
|
1468
|
+
self.traj_file = os.path.abspath(os.path.join(self.BPA_FOLDER_DIRECTORY, traj_filename))
|
|
1469
|
+
|
|
1470
|
+
print("Optimized structure file path:", self.optimized_struct_file)
|
|
1471
|
+
print("Trajectory file path:", self.traj_file)
|
|
1472
|
+
|
|
1473
|
+
else:
|
|
1474
|
+
print("Error: BPA_FOLDER_DIRECTORY or START_FILE is not set. Please run optimize() or optimize_oniom() first.")
|
|
1475
|
+
self.optimized_struct_file = None
|
|
1476
|
+
self.traj_file = None
|
|
1477
|
+
|
|
1478
|
+
except Exception as e:
|
|
1479
|
+
print(f"Error setting result file paths: {e}")
|
|
1480
|
+
self.optimized_struct_file = None
|
|
1481
|
+
self.traj_file = None
|
|
1482
|
+
|
|
1483
|
+
return
|
|
1484
|
+
|
|
1485
|
+
def run(self):
|
|
1486
|
+
# (Reads self.config)
|
|
1487
|
+
if type(self.config.args.INPUT) is str:
|
|
1488
|
+
START_FILE_LIST = [self.config.args.INPUT]
|
|
1489
|
+
else:
|
|
1490
|
+
START_FILE_LIST = self.config.args.INPUT
|
|
1491
|
+
|
|
1492
|
+
job_file_list = []
|
|
1493
|
+
|
|
1494
|
+
for job_file in START_FILE_LIST:
|
|
1495
|
+
print()
|
|
1496
|
+
if "*" in job_file:
|
|
1497
|
+
result_list = glob.glob(job_file)
|
|
1498
|
+
job_file_list = job_file_list + result_list
|
|
1499
|
+
else:
|
|
1500
|
+
job_file_list = job_file_list + [job_file]
|
|
1501
|
+
|
|
1502
|
+
for file in job_file_list:
|
|
1503
|
+
print("********************************")
|
|
1504
|
+
print(file)
|
|
1505
|
+
print("********************************")
|
|
1506
|
+
if os.path.exists(file) == False:
|
|
1507
|
+
print(f"{file} does not exist.")
|
|
1508
|
+
continue
|
|
1509
|
+
|
|
1510
|
+
# This creates the directory and sets self.START_FILE
|
|
1511
|
+
self._make_init_directory(file)
|
|
1512
|
+
|
|
1513
|
+
# Run the main optimization, which will create its own state
|
|
1514
|
+
if len(self.config.args.oniom_flag) > 0:
|
|
1515
|
+
self.optimize_oniom()
|
|
1516
|
+
else:
|
|
1517
|
+
self.optimize()
|
|
1518
|
+
|
|
1519
|
+
# Post-processing (relies on self.state being set by optimize())
|
|
1520
|
+
if self.state and self.config.CMDS:
|
|
1521
|
+
CMDPA = CMDSPathAnalysis(self.BPA_FOLDER_DIRECTORY, self.state.ENERGY_LIST_FOR_PLOTTING, self.state.BIAS_ENERGY_LIST_FOR_PLOTTING)
|
|
1522
|
+
CMDPA.main()
|
|
1523
|
+
if self.state and self.config.PCA:
|
|
1524
|
+
PCAPA = PCAPathAnalysis(self.BPA_FOLDER_DIRECTORY, self.state.ENERGY_LIST_FOR_PLOTTING, self.state.BIAS_ENERGY_LIST_FOR_PLOTTING)
|
|
1525
|
+
PCAPA.main()
|
|
1526
|
+
|
|
1527
|
+
if self.state and len(self.config.irc) > 0:
|
|
1528
|
+
if self.config.args.usextb != "None":
|
|
1529
|
+
xtb_method = self.config.args.usextb
|
|
1530
|
+
else:
|
|
1531
|
+
xtb_method = "None"
|
|
1532
|
+
|
|
1533
|
+
if self.state.iter % self.config.FC_COUNT == 0:
|
|
1534
|
+
hessian = self.state.Model_hess
|
|
1535
|
+
else:
|
|
1536
|
+
hessian = None
|
|
1537
|
+
|
|
1538
|
+
EXEC_IRC = IRC(self.BPA_FOLDER_DIRECTORY, self.state.final_file_directory,
|
|
1539
|
+
self.config.irc, self.SP, self.element_list,
|
|
1540
|
+
self.config.electric_charge_and_multiplicity, # This was from config
|
|
1541
|
+
force_data_parser(self.config.args), # Re-parse force_data
|
|
1542
|
+
xtb_method, FC_count=int(self.config.FC_COUNT), hessian=hessian)
|
|
1543
|
+
EXEC_IRC.run()
|
|
1544
|
+
self.irc_terminal_struct_paths = EXEC_IRC.terminal_struct_paths
|
|
1545
|
+
else:
|
|
1546
|
+
self.irc_terminal_struct_paths = []
|
|
1547
|
+
|
|
1548
|
+
print(f"Trial of geometry optimization ({file}) was completed.")
|
|
1549
|
+
|
|
1550
|
+
print("All calculations were completed.")
|
|
1551
|
+
|
|
1552
|
+
self.get_result_file_path()
|
|
1553
|
+
return
|