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.
Files changed (246) hide show
  1. multioptpy/Calculator/__init__.py +0 -0
  2. multioptpy/Calculator/ase_calculation_tools.py +424 -0
  3. multioptpy/Calculator/ase_tools/__init__.py +0 -0
  4. multioptpy/Calculator/ase_tools/fairchem.py +28 -0
  5. multioptpy/Calculator/ase_tools/gamess.py +19 -0
  6. multioptpy/Calculator/ase_tools/gaussian.py +165 -0
  7. multioptpy/Calculator/ase_tools/mace.py +28 -0
  8. multioptpy/Calculator/ase_tools/mopac.py +19 -0
  9. multioptpy/Calculator/ase_tools/nwchem.py +31 -0
  10. multioptpy/Calculator/ase_tools/orca.py +22 -0
  11. multioptpy/Calculator/ase_tools/pygfn0.py +37 -0
  12. multioptpy/Calculator/dxtb_calculation_tools.py +344 -0
  13. multioptpy/Calculator/emt_calculation_tools.py +458 -0
  14. multioptpy/Calculator/gpaw_calculation_tools.py +183 -0
  15. multioptpy/Calculator/lj_calculation_tools.py +314 -0
  16. multioptpy/Calculator/psi4_calculation_tools.py +334 -0
  17. multioptpy/Calculator/pwscf_calculation_tools.py +189 -0
  18. multioptpy/Calculator/pyscf_calculation_tools.py +327 -0
  19. multioptpy/Calculator/sqm1_calculation_tools.py +611 -0
  20. multioptpy/Calculator/sqm2_calculation_tools.py +376 -0
  21. multioptpy/Calculator/tblite_calculation_tools.py +352 -0
  22. multioptpy/Calculator/tersoff_calculation_tools.py +818 -0
  23. multioptpy/Constraint/__init__.py +0 -0
  24. multioptpy/Constraint/constraint_condition.py +834 -0
  25. multioptpy/Coordinate/__init__.py +0 -0
  26. multioptpy/Coordinate/polar_coordinate.py +199 -0
  27. multioptpy/Coordinate/redundant_coordinate.py +638 -0
  28. multioptpy/IRC/__init__.py +0 -0
  29. multioptpy/IRC/converge_criteria.py +28 -0
  30. multioptpy/IRC/dvv.py +544 -0
  31. multioptpy/IRC/euler.py +439 -0
  32. multioptpy/IRC/hpc.py +564 -0
  33. multioptpy/IRC/lqa.py +540 -0
  34. multioptpy/IRC/modekill.py +662 -0
  35. multioptpy/IRC/rk4.py +579 -0
  36. multioptpy/Interpolation/__init__.py +0 -0
  37. multioptpy/Interpolation/adaptive_interpolation.py +283 -0
  38. multioptpy/Interpolation/binomial_interpolation.py +179 -0
  39. multioptpy/Interpolation/geodesic_interpolation.py +785 -0
  40. multioptpy/Interpolation/interpolation.py +156 -0
  41. multioptpy/Interpolation/linear_interpolation.py +473 -0
  42. multioptpy/Interpolation/savitzky_golay_interpolation.py +252 -0
  43. multioptpy/Interpolation/spline_interpolation.py +353 -0
  44. multioptpy/MD/__init__.py +0 -0
  45. multioptpy/MD/thermostat.py +185 -0
  46. multioptpy/MEP/__init__.py +0 -0
  47. multioptpy/MEP/pathopt_bneb_force.py +443 -0
  48. multioptpy/MEP/pathopt_dmf_force.py +448 -0
  49. multioptpy/MEP/pathopt_dneb_force.py +130 -0
  50. multioptpy/MEP/pathopt_ewbneb_force.py +207 -0
  51. multioptpy/MEP/pathopt_gpneb_force.py +512 -0
  52. multioptpy/MEP/pathopt_lup_force.py +113 -0
  53. multioptpy/MEP/pathopt_neb_force.py +225 -0
  54. multioptpy/MEP/pathopt_nesb_force.py +205 -0
  55. multioptpy/MEP/pathopt_om_force.py +153 -0
  56. multioptpy/MEP/pathopt_qsm_force.py +174 -0
  57. multioptpy/MEP/pathopt_qsmv2_force.py +304 -0
  58. multioptpy/ModelFunction/__init__.py +7 -0
  59. multioptpy/ModelFunction/avoiding_model_function.py +29 -0
  60. multioptpy/ModelFunction/binary_image_ts_search_model_function.py +47 -0
  61. multioptpy/ModelFunction/conical_model_function.py +26 -0
  62. multioptpy/ModelFunction/opt_meci.py +50 -0
  63. multioptpy/ModelFunction/opt_mesx.py +47 -0
  64. multioptpy/ModelFunction/opt_mesx_2.py +49 -0
  65. multioptpy/ModelFunction/seam_model_function.py +27 -0
  66. multioptpy/ModelHessian/__init__.py +0 -0
  67. multioptpy/ModelHessian/approx_hessian.py +147 -0
  68. multioptpy/ModelHessian/calc_params.py +227 -0
  69. multioptpy/ModelHessian/fischer.py +236 -0
  70. multioptpy/ModelHessian/fischerd3.py +360 -0
  71. multioptpy/ModelHessian/fischerd4.py +398 -0
  72. multioptpy/ModelHessian/gfn0xtb.py +633 -0
  73. multioptpy/ModelHessian/gfnff.py +709 -0
  74. multioptpy/ModelHessian/lindh.py +165 -0
  75. multioptpy/ModelHessian/lindh2007d2.py +707 -0
  76. multioptpy/ModelHessian/lindh2007d3.py +822 -0
  77. multioptpy/ModelHessian/lindh2007d4.py +1030 -0
  78. multioptpy/ModelHessian/morse.py +106 -0
  79. multioptpy/ModelHessian/schlegel.py +144 -0
  80. multioptpy/ModelHessian/schlegeld3.py +322 -0
  81. multioptpy/ModelHessian/schlegeld4.py +559 -0
  82. multioptpy/ModelHessian/shortrange.py +346 -0
  83. multioptpy/ModelHessian/swartd2.py +496 -0
  84. multioptpy/ModelHessian/swartd3.py +706 -0
  85. multioptpy/ModelHessian/swartd4.py +918 -0
  86. multioptpy/ModelHessian/tshess.py +40 -0
  87. multioptpy/Optimizer/QHAdam.py +61 -0
  88. multioptpy/Optimizer/__init__.py +0 -0
  89. multioptpy/Optimizer/abc_fire.py +83 -0
  90. multioptpy/Optimizer/adabelief.py +58 -0
  91. multioptpy/Optimizer/adabound.py +68 -0
  92. multioptpy/Optimizer/adadelta.py +65 -0
  93. multioptpy/Optimizer/adaderivative.py +56 -0
  94. multioptpy/Optimizer/adadiff.py +68 -0
  95. multioptpy/Optimizer/adafactor.py +70 -0
  96. multioptpy/Optimizer/adam.py +65 -0
  97. multioptpy/Optimizer/adamax.py +62 -0
  98. multioptpy/Optimizer/adamod.py +83 -0
  99. multioptpy/Optimizer/adamw.py +65 -0
  100. multioptpy/Optimizer/adiis.py +523 -0
  101. multioptpy/Optimizer/afire_neb.py +282 -0
  102. multioptpy/Optimizer/block_hessian_update.py +709 -0
  103. multioptpy/Optimizer/c2diis.py +491 -0
  104. multioptpy/Optimizer/component_wise_scaling.py +405 -0
  105. multioptpy/Optimizer/conjugate_gradient.py +82 -0
  106. multioptpy/Optimizer/conjugate_gradient_neb.py +345 -0
  107. multioptpy/Optimizer/coordinate_locking.py +405 -0
  108. multioptpy/Optimizer/dic_rsirfo.py +1015 -0
  109. multioptpy/Optimizer/ediis.py +417 -0
  110. multioptpy/Optimizer/eve.py +76 -0
  111. multioptpy/Optimizer/fastadabelief.py +61 -0
  112. multioptpy/Optimizer/fire.py +77 -0
  113. multioptpy/Optimizer/fire2.py +249 -0
  114. multioptpy/Optimizer/fire_neb.py +92 -0
  115. multioptpy/Optimizer/gan_step.py +486 -0
  116. multioptpy/Optimizer/gdiis.py +609 -0
  117. multioptpy/Optimizer/gediis.py +203 -0
  118. multioptpy/Optimizer/geodesic_step.py +433 -0
  119. multioptpy/Optimizer/gpmin.py +633 -0
  120. multioptpy/Optimizer/gpr_step.py +364 -0
  121. multioptpy/Optimizer/gradientdescent.py +78 -0
  122. multioptpy/Optimizer/gradientdescent_neb.py +52 -0
  123. multioptpy/Optimizer/hessian_update.py +433 -0
  124. multioptpy/Optimizer/hybrid_rfo.py +998 -0
  125. multioptpy/Optimizer/kdiis.py +625 -0
  126. multioptpy/Optimizer/lars.py +21 -0
  127. multioptpy/Optimizer/lbfgs.py +253 -0
  128. multioptpy/Optimizer/lbfgs_neb.py +355 -0
  129. multioptpy/Optimizer/linesearch.py +236 -0
  130. multioptpy/Optimizer/lookahead.py +40 -0
  131. multioptpy/Optimizer/nadam.py +64 -0
  132. multioptpy/Optimizer/newton.py +200 -0
  133. multioptpy/Optimizer/prodigy.py +70 -0
  134. multioptpy/Optimizer/purtubation.py +16 -0
  135. multioptpy/Optimizer/quickmin_neb.py +245 -0
  136. multioptpy/Optimizer/radam.py +75 -0
  137. multioptpy/Optimizer/rfo_neb.py +302 -0
  138. multioptpy/Optimizer/ric_rfo.py +842 -0
  139. multioptpy/Optimizer/rl_step.py +627 -0
  140. multioptpy/Optimizer/rmspropgrave.py +65 -0
  141. multioptpy/Optimizer/rsirfo.py +1647 -0
  142. multioptpy/Optimizer/rsprfo.py +1056 -0
  143. multioptpy/Optimizer/sadam.py +60 -0
  144. multioptpy/Optimizer/samsgrad.py +63 -0
  145. multioptpy/Optimizer/tr_lbfgs.py +678 -0
  146. multioptpy/Optimizer/trim.py +273 -0
  147. multioptpy/Optimizer/trust_radius.py +207 -0
  148. multioptpy/Optimizer/trust_radius_neb.py +121 -0
  149. multioptpy/Optimizer/yogi.py +60 -0
  150. multioptpy/OtherMethod/__init__.py +0 -0
  151. multioptpy/OtherMethod/addf.py +1150 -0
  152. multioptpy/OtherMethod/dimer.py +895 -0
  153. multioptpy/OtherMethod/elastic_image_pair.py +629 -0
  154. multioptpy/OtherMethod/modelfunction.py +456 -0
  155. multioptpy/OtherMethod/newton_traj.py +454 -0
  156. multioptpy/OtherMethod/twopshs.py +1095 -0
  157. multioptpy/PESAnalyzer/__init__.py +0 -0
  158. multioptpy/PESAnalyzer/calc_irc_curvature.py +125 -0
  159. multioptpy/PESAnalyzer/cmds_analysis.py +152 -0
  160. multioptpy/PESAnalyzer/koopman_analysis.py +268 -0
  161. multioptpy/PESAnalyzer/pca_analysis.py +314 -0
  162. multioptpy/Parameters/__init__.py +0 -0
  163. multioptpy/Parameters/atomic_mass.py +20 -0
  164. multioptpy/Parameters/atomic_number.py +22 -0
  165. multioptpy/Parameters/covalent_radii.py +44 -0
  166. multioptpy/Parameters/d2.py +61 -0
  167. multioptpy/Parameters/d3.py +63 -0
  168. multioptpy/Parameters/d4.py +103 -0
  169. multioptpy/Parameters/dreiding.py +34 -0
  170. multioptpy/Parameters/gfn0xtb_param.py +137 -0
  171. multioptpy/Parameters/gfnff_param.py +315 -0
  172. multioptpy/Parameters/gnb.py +104 -0
  173. multioptpy/Parameters/parameter.py +22 -0
  174. multioptpy/Parameters/uff.py +72 -0
  175. multioptpy/Parameters/unit_values.py +20 -0
  176. multioptpy/Potential/AFIR_potential.py +55 -0
  177. multioptpy/Potential/LJ_repulsive_potential.py +345 -0
  178. multioptpy/Potential/__init__.py +0 -0
  179. multioptpy/Potential/anharmonic_keep_potential.py +28 -0
  180. multioptpy/Potential/asym_elllipsoidal_potential.py +718 -0
  181. multioptpy/Potential/electrostatic_potential.py +69 -0
  182. multioptpy/Potential/flux_potential.py +30 -0
  183. multioptpy/Potential/gaussian_potential.py +101 -0
  184. multioptpy/Potential/idpp.py +516 -0
  185. multioptpy/Potential/keep_angle_potential.py +146 -0
  186. multioptpy/Potential/keep_dihedral_angle_potential.py +105 -0
  187. multioptpy/Potential/keep_outofplain_angle_potential.py +70 -0
  188. multioptpy/Potential/keep_potential.py +99 -0
  189. multioptpy/Potential/mechano_force_potential.py +74 -0
  190. multioptpy/Potential/nanoreactor_potential.py +52 -0
  191. multioptpy/Potential/potential.py +896 -0
  192. multioptpy/Potential/spacer_model_potential.py +221 -0
  193. multioptpy/Potential/switching_potential.py +258 -0
  194. multioptpy/Potential/universal_potential.py +34 -0
  195. multioptpy/Potential/value_range_potential.py +36 -0
  196. multioptpy/Potential/void_point_potential.py +25 -0
  197. multioptpy/SQM/__init__.py +0 -0
  198. multioptpy/SQM/sqm1/__init__.py +0 -0
  199. multioptpy/SQM/sqm1/sqm1_core.py +1792 -0
  200. multioptpy/SQM/sqm2/__init__.py +0 -0
  201. multioptpy/SQM/sqm2/calc_tools.py +95 -0
  202. multioptpy/SQM/sqm2/sqm2_basis.py +850 -0
  203. multioptpy/SQM/sqm2/sqm2_bond.py +119 -0
  204. multioptpy/SQM/sqm2/sqm2_core.py +303 -0
  205. multioptpy/SQM/sqm2/sqm2_data.py +1229 -0
  206. multioptpy/SQM/sqm2/sqm2_disp.py +65 -0
  207. multioptpy/SQM/sqm2/sqm2_eeq.py +243 -0
  208. multioptpy/SQM/sqm2/sqm2_overlapint.py +704 -0
  209. multioptpy/SQM/sqm2/sqm2_qm.py +578 -0
  210. multioptpy/SQM/sqm2/sqm2_rep.py +66 -0
  211. multioptpy/SQM/sqm2/sqm2_srb.py +70 -0
  212. multioptpy/Thermo/__init__.py +0 -0
  213. multioptpy/Thermo/normal_mode_analyzer.py +865 -0
  214. multioptpy/Utils/__init__.py +0 -0
  215. multioptpy/Utils/bond_connectivity.py +264 -0
  216. multioptpy/Utils/calc_tools.py +884 -0
  217. multioptpy/Utils/oniom.py +96 -0
  218. multioptpy/Utils/pbc.py +48 -0
  219. multioptpy/Utils/riemann_curvature.py +208 -0
  220. multioptpy/Utils/symmetry_analyzer.py +482 -0
  221. multioptpy/Visualization/__init__.py +0 -0
  222. multioptpy/Visualization/visualization.py +156 -0
  223. multioptpy/WFAnalyzer/MO_analysis.py +104 -0
  224. multioptpy/WFAnalyzer/__init__.py +0 -0
  225. multioptpy/Wrapper/__init__.py +0 -0
  226. multioptpy/Wrapper/autots.py +1239 -0
  227. multioptpy/Wrapper/ieip_wrapper.py +93 -0
  228. multioptpy/Wrapper/md_wrapper.py +92 -0
  229. multioptpy/Wrapper/neb_wrapper.py +94 -0
  230. multioptpy/Wrapper/optimize_wrapper.py +76 -0
  231. multioptpy/__init__.py +5 -0
  232. multioptpy/entrypoints.py +916 -0
  233. multioptpy/fileio.py +660 -0
  234. multioptpy/ieip.py +340 -0
  235. multioptpy/interface.py +1086 -0
  236. multioptpy/irc.py +529 -0
  237. multioptpy/moleculardynamics.py +432 -0
  238. multioptpy/neb.py +1267 -0
  239. multioptpy/optimization.py +1553 -0
  240. multioptpy/optimizer.py +709 -0
  241. multioptpy-1.20.2.dist-info/METADATA +438 -0
  242. multioptpy-1.20.2.dist-info/RECORD +246 -0
  243. multioptpy-1.20.2.dist-info/WHEEL +5 -0
  244. multioptpy-1.20.2.dist-info/entry_points.txt +9 -0
  245. multioptpy-1.20.2.dist-info/licenses/LICENSE +674 -0
  246. multioptpy-1.20.2.dist-info/top_level.txt +1 -0
multioptpy/fileio.py ADDED
@@ -0,0 +1,660 @@
1
+ import glob
2
+ import os
3
+ import re
4
+ import numpy as np
5
+
6
+ from scipy.signal import argrelextrema
7
+ from multioptpy.Utils.calc_tools import calc_RMS
8
+
9
+ def save_bias_pot_info(file_path, energy, gradient, bias_pot_id):
10
+ max_grad = np.max(np.abs(gradient))
11
+ rms_grad = calc_RMS(gradient)
12
+ save_path = file_path+"bias_pot_info_"+str(bias_pot_id)+".log"
13
+
14
+ if not os.path.exists(save_path):
15
+ with open(save_path, "w") as f:
16
+ f.write("Energy, MaxGrad, RMSGrad\n")
17
+
18
+ with open(save_path, "a") as f:
19
+ f.write(str(energy)+","+str(max_grad)+","+str(rms_grad)+"\n")
20
+ return
21
+
22
+ def save_bias_param_grad_info(file_path, gradient, bias_pot_id):
23
+ save_path = file_path+"bias_param_grad_info_"+str(bias_pot_id)+".log"
24
+
25
+ if not os.path.exists(save_path):
26
+ with open(save_path, "w") as f:
27
+ f.write("Gradient\n")
28
+ with open(save_path, "a") as f:
29
+ f.write(str(gradient)+"\n")
30
+ return
31
+
32
+ def read_software_path(file_path="./software_path.conf"):
33
+ print("Reading software path from", file_path)
34
+ with open(file_path, "r") as f:
35
+ words = f.read().splitlines()
36
+ software_path_dict = {}
37
+ for word in words:
38
+ tmp_split = word.split("::")
39
+ soft_name = tmp_split[0]
40
+ soft_path = tmp_split[1]
41
+ software_path_dict[soft_name] = soft_path
42
+ return software_path_dict
43
+
44
+ def xyz2list(file_path, args_electric_charge_and_multiplicity):
45
+ pattern_cs = get_pattern_cs()
46
+ pattern_xyz = get_pattern_xyz()
47
+ electric_charge_and_multiplicity = None
48
+ element_list = []
49
+ with open(file_path, "r") as f:
50
+ words = f.read().splitlines()
51
+ geometry_list = []
52
+ for word in words:
53
+ if re.match(pattern_cs, word):
54
+ electric_charge_and_multiplicity = list(map(str, word.split()))
55
+ if re.match(pattern_xyz, word):
56
+ geometry_list.append(word.split()[1:4])
57
+ element_list.append(word.split()[0])
58
+ if electric_charge_and_multiplicity is None:
59
+ electric_charge_and_multiplicity = args_electric_charge_and_multiplicity
60
+
61
+ return geometry_list, element_list, electric_charge_and_multiplicity
62
+ import re
63
+
64
+
65
+
66
+ def _parse_gamess(lines):
67
+ """Internal function to parse GAMESS input."""
68
+ pattern_atom = get_pattern_gamess_atom()
69
+ element_list = []
70
+ geometry_list = []
71
+
72
+ is_data_section = False
73
+ for line in lines:
74
+ if "$DATA" in line.upper(): is_data_section = True; continue
75
+ if "$END" in line.upper() and is_data_section: break
76
+ if is_data_section:
77
+ match = pattern_atom.match(line)
78
+ if match:
79
+ element_list.append(match.group(1))
80
+ geometry_list.append([match.group(2), match.group(3), match.group(4)])
81
+ return geometry_list, element_list
82
+
83
+ def _parse_orca(lines):
84
+ """Internal function to parse ORCA input."""
85
+ pattern_atom = get_pattern_orca_atom()
86
+ element_list = []
87
+ geometry_list = []
88
+ electric_charge_and_multiplicity = ["0", "1"] # Default
89
+
90
+ is_coord_section = False
91
+ for line in lines:
92
+ # Check for start of coordinate block, e.g., *xyz 0 1
93
+ if line.strip().startswith("*xyz"):
94
+ is_coord_section = True
95
+ parts = line.strip().split()
96
+ if len(parts) == 3:
97
+ electric_charge_and_multiplicity = [parts[1], parts[2]]
98
+ continue
99
+
100
+ # Check for end of coordinate block
101
+ if is_coord_section and line.strip() == "*":
102
+ break
103
+
104
+ if is_coord_section:
105
+ match = pattern_atom.match(line)
106
+ if match:
107
+ element_list.append(match.group(1))
108
+ geometry_list.append([match.group(2), match.group(3), match.group(4)])
109
+ return geometry_list, element_list, electric_charge_and_multiplicity
110
+
111
+ def _parse_qchem(lines):
112
+ """Internal function to parse Q-Chem input."""
113
+ pattern_atom = get_pattern_qchem_atom()
114
+ element_list = []
115
+ geometry_list = []
116
+ electric_charge_and_multiplicity = ["0", "1"] # Default
117
+
118
+ is_molecule_section = False
119
+ for line in lines:
120
+ if "$molecule" in line.lower():
121
+ is_molecule_section = True
122
+ # Read charge and multiplicity from the next line
123
+ charge_mult_line = next(iter(lines), "").strip()
124
+ parts = charge_mult_line.split()
125
+ if len(parts) == 2:
126
+ electric_charge_and_multiplicity = parts
127
+ continue
128
+
129
+ if "$end" in line.lower() and is_molecule_section:
130
+ break
131
+
132
+ if is_molecule_section:
133
+ # Skip the charge/multiplicity line itself
134
+ if re.match(r"^\s*[+-]?\d+\s+[+-]?\d+\s*$", line.strip()):
135
+ continue
136
+ match = pattern_atom.match(line)
137
+ if match:
138
+ element_list.append(match.group(1))
139
+ geometry_list.append([match.group(2), match.group(3), match.group(4)])
140
+ return geometry_list, element_list, electric_charge_and_multiplicity
141
+
142
+
143
+ def inp2list(file_path, args_electric_charge_and_multiplicity=["0", "1"]):
144
+ """
145
+ Automatically detects the input file format (GAMESS, ORCA, Q-Chem)
146
+ and parses the atomic coordinates.
147
+ """
148
+ with open(file_path, 'r') as f:
149
+ content = f.read()
150
+
151
+ lines = content.splitlines()
152
+
153
+ # --- Format Detection Logic ---
154
+ detected_format = None
155
+ if "$CONTRL" in content and "$DATA" in content:
156
+ detected_format = "gamess"
157
+ elif re.search(r"^\s*!", content, re.MULTILINE) and "*xyz" in content:
158
+ detected_format = "orca"
159
+ elif "$molecule" in content:
160
+ detected_format = "qchem"
161
+ else:
162
+ print("Error: Could not determine the file format.")
163
+ return [], [], None, None
164
+
165
+ # --- Parsing based on detected format ---
166
+ if detected_format == "gamess":
167
+ print("Detected format: GAMESS")
168
+ geometry_list, element_list = _parse_gamess(lines)
169
+ # GAMESS does not have a standard charge/multiplicity line in $DATA
170
+ electric_charge_and_multiplicity = args_electric_charge_and_multiplicity
171
+
172
+ elif detected_format == "orca":
173
+ print("Detected format: ORCA")
174
+ geometry_list, element_list, electric_charge_and_multiplicity = _parse_orca(lines)
175
+
176
+ elif detected_format == "qchem":
177
+ print("Detected format: Q-Chem")
178
+ geometry_list, element_list, electric_charge_and_multiplicity = _parse_qchem(lines)
179
+
180
+ return geometry_list, element_list, electric_charge_and_multiplicity
181
+
182
+ def mol2list(file_path, args_electric_charge_and_multiplicity):
183
+ """Parses a MOL file (.mol)."""
184
+ pattern_atom = get_pattern_mol_atom()
185
+
186
+ element_list = []
187
+ geometry_list = []
188
+ electric_charge_and_multiplicity = args_electric_charge_and_multiplicity
189
+
190
+ with open(file_path, "r") as f:
191
+ lines = f.readlines()
192
+
193
+ # Get the number of atoms from the counts line (4th line)
194
+ try:
195
+ num_atoms = int(lines[3].strip().split()[0])
196
+ except (IndexError, ValueError):
197
+ # Return empty lists for an invalid format
198
+ return [], [], electric_charge_and_multiplicity
199
+
200
+ # Process the atom block
201
+ atom_block_lines = lines[4 : 4 + num_atoms]
202
+ for line in atom_block_lines:
203
+ match = pattern_atom.match(line)
204
+ if match:
205
+ # MOL format order is: X, Y, Z, Symbol
206
+ geometry_list.append([match.group(1), match.group(2), match.group(3)])
207
+ element_list.append(match.group(4))
208
+
209
+ return geometry_list, element_list, electric_charge_and_multiplicity
210
+
211
+ def mol22list(file_path, args_electric_charge_and_multiplicity):
212
+ """Parses a MOL2 file (.mol2)."""
213
+ pattern_atom = get_pattern_mol2_atom()
214
+
215
+ element_list = []
216
+ geometry_list = []
217
+ electric_charge_and_multiplicity = args_electric_charge_and_multiplicity
218
+
219
+ is_atom_section = False
220
+ with open(file_path, "r") as f:
221
+ for line in f:
222
+ # Detect the start of the ATOM section
223
+ if "@<TRIPOS>ATOM" in line:
224
+ is_atom_section = True
225
+ continue
226
+
227
+ # End processing if another section starts
228
+ if is_atom_section and "@<TRIPOS>" in line:
229
+ break
230
+
231
+ # Process atom lines within the ATOM section
232
+ if is_atom_section:
233
+ match = pattern_atom.match(line)
234
+ if match:
235
+ # Extract the element symbol from the atom name (e.g., "C1", "Oa")
236
+ atom_name = match.group(1)
237
+ element = "".join(filter(str.isalpha, atom_name))
238
+ element_list.append(element)
239
+
240
+ # MOL2 format order is: Name, X, Y, Z
241
+ geometry_list.append([match.group(2), match.group(3), match.group(4)])
242
+
243
+ return geometry_list, element_list, electric_charge_and_multiplicity
244
+
245
+ def traj2list(file_path, args_electric_charge_and_multiplicity):
246
+ pattern_cs = get_pattern_cs()
247
+ pattern_xyz = get_pattern_xyz()
248
+
249
+ electric_charge_and_multiplicity = None
250
+ cs_flag = True
251
+
252
+ with open(file_path, "r") as f:
253
+ words = f.read().splitlines()
254
+
255
+ geometry_list = []
256
+ element_list = []
257
+ geometries = []
258
+ elements = []
259
+ for word in words:
260
+ if re.match(pattern_cs, word) and cs_flag:
261
+ electric_charge_and_multiplicity = list(map(str, word.split()))
262
+ cs_flag = False
263
+ if re.match(pattern_xyz, word):
264
+ geometry_list.append(word.split()[1:4])
265
+ element_list.append(word.split()[0])
266
+ else:
267
+ if len(geometry_list) != 0:
268
+ geometries.append(geometry_list)
269
+ if len(element_list) != 0:
270
+ elements.append(element_list)
271
+
272
+ geometry_list = []
273
+ element_list = []
274
+
275
+ if electric_charge_and_multiplicity is None:
276
+ electric_charge_and_multiplicity = args_electric_charge_and_multiplicity
277
+
278
+ return geometries, elements, electric_charge_and_multiplicity
279
+
280
+ def get_pattern_xyz():
281
+ pattern_xyz = re.compile(r"\s*([A-Za-z]+)\s+([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?)(?:\s+([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?))(?:\s+([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?))\s*")
282
+ return pattern_xyz
283
+
284
+ def get_pattern_cs():
285
+ pattern_cs = re.compile(r"-*[0-9]+\s+-*[0-9]+\s*")
286
+ return pattern_cs
287
+
288
+
289
+ def get_pattern_qchem_atom():
290
+ """Returns a regex pattern for Q-Chem atom lines."""
291
+ coordinate_pattern = r"([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?)"
292
+ pattern = re.compile(
293
+ r"^\s*([A-Za-z]+)\s+" # Element
294
+ + coordinate_pattern + r"\s+" # X
295
+ + coordinate_pattern + r"\s+" # Y
296
+ + coordinate_pattern + r"\s*" # Z
297
+ )
298
+ return pattern
299
+
300
+ def get_pattern_orca_atom():
301
+ """Returns a regex pattern for ORCA atom lines."""
302
+ coordinate_pattern = r"([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?)"
303
+ pattern = re.compile(
304
+ r"^\s*([A-Za-z]+)\s+" # Element
305
+ + coordinate_pattern + r"\s+" # X
306
+ + coordinate_pattern + r"\s+" # Y
307
+ + coordinate_pattern + r"\s*" # Z
308
+ )
309
+ return pattern
310
+
311
+ def get_pattern_gamess_atom():
312
+ """
313
+ Returns a regex pattern to match atom lines in a GAMESS input file.
314
+ Supports both decimal and scientific notation for coordinates.
315
+ """
316
+ # Example: "O 8.0 0.0 0.0 0.0" or "C 6.0 1.234e+01 -5.67E-02 8.9"
317
+ coordinate_pattern = r"([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?)"
318
+
319
+ pattern = re.compile(
320
+ r"^\s*([A-Za-z]+)\s+" # Element
321
+ r"[+-]?\d+\.\d+\s+" # Atomic Number
322
+ + coordinate_pattern + r"\s+" # X coordinate
323
+ + coordinate_pattern + r"\s+" # Y coordinate
324
+ + coordinate_pattern + r"\s*" # Z coordinate
325
+ )
326
+ return pattern
327
+
328
+
329
+ def get_pattern_mol_atom():
330
+ """
331
+ Returns a regex pattern to match atom lines in a MOL/SDF file.
332
+ Supports both decimal and scientific notation for coordinates.
333
+ """
334
+ # Example: " 0.0000 0.0000 0.0000 O ..." or " 1.23e-05 -4.56E+00 7.89 O ..."
335
+ coordinate_pattern = r"([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?)"
336
+
337
+ pattern = re.compile(
338
+ r"^\s*" + coordinate_pattern + r"\s+" # X coordinate
339
+ + coordinate_pattern + r"\s+" # Y coordinate
340
+ + coordinate_pattern + r"\s+" # Z coordinate
341
+ r"([A-Za-z]+)\s+.*" # Element
342
+ )
343
+ return pattern
344
+
345
+ def get_pattern_mol2_atom():
346
+ """
347
+ Returns a regex pattern to match atom lines in a MOL2 file.
348
+ Supports both decimal and scientific notation for coordinates.
349
+ """
350
+ # Example: " 1 O 0.0000 0.0000 0.0000 O.3 ..." or " 2 C 1.2e1 -3.4E-1 5.6 C.ar ..."
351
+ coordinate_pattern = r"([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?)"
352
+
353
+ pattern = re.compile(
354
+ r"^\s*\d+\s+" # Atom ID
355
+ r"([A-Za-z]+)\w*\s+" # Atom Name
356
+ + coordinate_pattern + r"\s+" # X coordinate
357
+ + coordinate_pattern + r"\s+" # Y coordinate
358
+ + coordinate_pattern + r"\s+.*" # Z coordinate
359
+ )
360
+ return pattern
361
+
362
+
363
+ class FileIO:
364
+ def __init__(self, folder_dir, file):
365
+ self.work_directory = folder_dir
366
+ self.START_FILE = file
367
+ self.NOEXT_START_FILE = os.path.splitext(os.path.basename(self.START_FILE))[0]
368
+ self.is_save_gjf_file = True
369
+ return
370
+
371
+ def make_geometry_list(self, args_electric_charge_and_multiplicity):
372
+ """Load initial structure"""
373
+ tmp_geometry_list, element_list, electric_charge_and_multiplicity = xyz2list(self.START_FILE, args_electric_charge_and_multiplicity)
374
+ natoms = len(tmp_geometry_list)
375
+
376
+ # Create start_data with list comprehension instead of for loop
377
+ start_data = [
378
+ str(natoms),
379
+ electric_charge_and_multiplicity,
380
+ *[[element_list[j]] + tmp_geometry_list[j] for j in range(len(tmp_geometry_list))]
381
+ ]
382
+
383
+ return [start_data], element_list, electric_charge_and_multiplicity
384
+
385
+
386
+ def print_geometry_list(self, new_geometry, element_list, electric_charge_and_multiplicity):
387
+ """load structure updated geometry for next QM calculation"""
388
+ new_geometry = new_geometry.tolist()
389
+ print("\n")
390
+
391
+ # Process all geometries at once with list comprehension
392
+ formatted_geometries = []
393
+ for num, geometry in enumerate(new_geometry):
394
+ element = element_list[num]
395
+ formatted_geometry = [element] + list(map(str, geometry))
396
+ formatted_geometries.append(formatted_geometry)
397
+ print(f"{element:2} {float(geometry[0]):>17.12f} {float(geometry[1]):>17.12f} {float(geometry[2]):>17.12f}")
398
+
399
+ geometry_list = [[electric_charge_and_multiplicity, *formatted_geometries]]
400
+ print("")
401
+
402
+ return geometry_list
403
+
404
+
405
+ def make_psi4_input_file(self, geometry_list, iter):#geometry_list: ang.
406
+ """structure updated geometry is saved."""
407
+ file_directory = self.work_directory+"samples_"+self.NOEXT_START_FILE+"_"+str(iter)
408
+ tmp_cs = ["SAMPLE"+str(iter), ""]
409
+
410
+
411
+ float_pattern = r"([+-]?(?:\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?)"
412
+
413
+ os.makedirs(file_directory, exist_ok=True)
414
+
415
+ for y, geometry in enumerate(geometry_list):
416
+ tmp_geometry = []
417
+ for geom in geometry:
418
+ if len(geom) == 4 \
419
+ and re.match(r"[A-Za-z]+", str(geom[0])) \
420
+ and all(re.match(float_pattern, str(x)) for x in geom[1:]):
421
+ tmp_geometry.append(geom)
422
+
423
+ if len(geom) == 2 and re.match(r"-*\d+", str(geom[0])) and re.match(r"-*\d+", str(geom[1])):
424
+ tmp_cs = geom
425
+
426
+ with open(file_directory+"/"+self.NOEXT_START_FILE+"_"+str(y)+".xyz","w") as w:
427
+ w.write(str(len(tmp_geometry))+"\n")
428
+ w.write(str(tmp_cs[0])+" "+str(tmp_cs[1])+"\n")
429
+ for rows in tmp_geometry:
430
+ w.write(f"{rows[0]:2} {float(rows[1]):>17.12f} {float(rows[2]):>17.12f} {float(rows[3]):>17.12f}\n")
431
+ return file_directory
432
+
433
+ def read_gjf_file(self, args_electric_charge_and_multiplicity=None):
434
+ geometry_list = []
435
+ element_list = []
436
+ with open(self.START_FILE, 'r') as f:
437
+ lines = f.read().splitlines()
438
+ read_flag = False
439
+ pattern = r"-*[0-9]+\s+-*[0-9]+\s*"
440
+ repatter = re.compile(pattern)
441
+ for i in range(len(lines)):
442
+ if bool(re.match(repatter, lines[i])) is True:
443
+ electric_charge_and_multiplicity = lines[i].split()
444
+ read_flag = True
445
+ geometry_list.append("dummy")
446
+ geometry_list.append(lines[i].split())
447
+ continue
448
+ if read_flag and len(lines[i]) == 0:
449
+ break
450
+ if read_flag:
451
+ element_list.append(lines[i].split()[0])
452
+ geometry_list.append(lines[i].split())
453
+ geometry_list = [geometry_list]
454
+ return geometry_list, element_list, electric_charge_and_multiplicity
455
+
456
+
457
+
458
+ def read_mol_file(self, args_electric_charge_and_multiplicity=None):
459
+ """
460
+ Reads a .mol file, formats output to match the gjf reader structure.
461
+ """
462
+ # Call the internal parser to get clean lists
463
+ coords_list, elements, charge_multiplicity = mol2list(
464
+ self.START_FILE, args_electric_charge_and_multiplicity
465
+ )
466
+
467
+ if not elements:
468
+ return [[]], [], charge_multiplicity
469
+
470
+ # Reconstruct the geometry_list to match the target format
471
+ # [["dummy", [charge, mult], [element, x, y, z], ...]]
472
+ output_geometry_list = ["dummy", charge_multiplicity]
473
+ for i, element in enumerate(elements):
474
+ full_atom_line = [element] + coords_list[i]
475
+ output_geometry_list.append(full_atom_line)
476
+
477
+ return [output_geometry_list], elements, charge_multiplicity
478
+
479
+ def read_mol2_file(self, args_electric_charge_and_multiplicity=None):
480
+ """
481
+ Reads a .mol2 file, formats output to match the gjf reader structure.
482
+ """
483
+ # Call the internal parser to get clean lists
484
+ coords_list, elements, charge_multiplicity = mol22list(
485
+ self.START_FILE, args_electric_charge_and_multiplicity
486
+ )
487
+
488
+ if not elements:
489
+ return [[]], [], charge_multiplicity
490
+
491
+ # Reconstruct the geometry_list to match the target format
492
+ output_geometry_list = ["dummy", charge_multiplicity]
493
+ for i, element in enumerate(elements):
494
+ full_atom_line = [element] + coords_list[i]
495
+ output_geometry_list.append(full_atom_line)
496
+
497
+ return [output_geometry_list], elements, charge_multiplicity
498
+
499
+ def read_gamess_inp_file(self, args_electric_charge_and_multiplicity=None):
500
+ """
501
+ Reads a .inp file, formats output to match the gjf reader structure.
502
+ """
503
+ # Call the internal parser to get clean lists
504
+ coords_list, elements, charge_multiplicity = inp2list(
505
+ self.START_FILE, args_electric_charge_and_multiplicity
506
+ )
507
+
508
+ if not elements:
509
+ return [[]], [], charge_multiplicity
510
+
511
+ # Reconstruct the geometry_list to match the target format
512
+ output_geometry_list = ["dummy", charge_multiplicity]
513
+ for i, element in enumerate(elements):
514
+ full_atom_line = [element] + coords_list[i]
515
+ output_geometry_list.append(full_atom_line)
516
+
517
+ return [output_geometry_list], elements, charge_multiplicity
518
+
519
+
520
+
521
+ def save_gjf_file(self, geometry_list):
522
+ with open(self.work_directory+self.NOEXT_START_FILE+".gjf","w") as f:
523
+ f.write("%mem=4GB\n")
524
+ f.write("%nprocshared=4\n")
525
+ f.write("#p B3LYP/6-31G* opt freq\n")
526
+ f.write("\n")
527
+ f.write("Title Card Required\n")
528
+ f.write("\n")
529
+ f.write("0 1\n")# This line is required for fixing the charge and multiplicity.
530
+ for geometry in geometry_list:
531
+ f.write(geometry)
532
+ f.write("\n")
533
+ f.write("\n\n\n")
534
+ return
535
+
536
+
537
+ def make_traj_file(self, name=""):
538
+ """optimized path is saved."""
539
+ print("\nprocessing geometry collection ...\n")
540
+ if name == "":
541
+ file_list = sum([sorted(glob.glob(os.path.join(self.work_directory, f"samples_*_" + "[0-9]" * i, "*.xyz")))
542
+ for i in range(1, 7)], [])
543
+ else:
544
+ file_list = sum([sorted(glob.glob(os.path.join(self.work_directory, f"samples_*_{name}_" + "[0-9]" * i, "*.xyz")))
545
+ for i in range(1, 7)], [])
546
+ step_num = len(file_list)
547
+
548
+ for m, file in enumerate(file_list):
549
+ sample = []
550
+ tmp_geometry_list, element_list, _ = xyz2list(file, None)
551
+ for j in range(len(element_list)):
552
+ sample.append(f"{element_list[j]:2} {float(tmp_geometry_list[j][0]):>17.12f} {float(tmp_geometry_list[j][1]):>17.12f} {float(tmp_geometry_list[j][2]):>17.12f}")
553
+ with open(self.work_directory+self.NOEXT_START_FILE+"_traj.xyz","a") as w:
554
+ atom_num = len(sample)
555
+ w.write(str(atom_num)+"\n")
556
+ w.write("Frame "+str(m)+"\n")
557
+ for i in sample:
558
+ if "\n" == i or "" == i:
559
+ continue
560
+ w.write(i+"\n")
561
+
562
+ if m == step_num - 1:
563
+ if self.is_save_gjf_file:
564
+ self.save_gjf_file(sample)
565
+ with open(self.work_directory+self.NOEXT_START_FILE+"_optimized.xyz","a") as w2:
566
+ w2.write(str(atom_num)+"\n")
567
+ w2.write("OptimizedStructure\n")
568
+ for i in sample:
569
+ if "\n" == i or "" == i:
570
+ continue
571
+ w2.write(i+"\n")
572
+ print("\ngeometry collection was completed...\n")
573
+ return
574
+
575
+ def xyz_file_save_for_IRC(self, element_list, geometry_list):
576
+ count = 0
577
+ for geometry in geometry_list:
578
+ with open(self.work_directory+"IRC_path.xyz","a") as w:
579
+ atom_num = len(geometry)
580
+ w.write(str(atom_num)+"\n")
581
+ w.write("Frame "+str(count)+"\n")
582
+ for i in range(len(geometry)):
583
+ w.write(f"{element_list[i]:2} {float(geometry[i][0]):>17.12f} {float(geometry[i][1]):>17.12f} {float(geometry[i][2]):>17.12f}\n")
584
+ count += 1
585
+ print("\ngeometry collection for IRC was completed...\n")
586
+ return
587
+
588
+ def make_traj_file_for_DM(self, img_1="reactant", img_2="product"):
589
+ """optimized path is saved."""
590
+ print("\nprocessing geometry collection ...\n")
591
+ file_list = sum(
592
+ [sorted(glob.glob(self.work_directory + f"samples_*_{str(img_1)}_{'[0-9]' * i}/*.xyz"))
593
+ for i in range(1, 7)],
594
+ []
595
+ ) + sum(
596
+ [sorted(glob.glob(self.work_directory + f"samples_*_{str(img_2)}_{'[0-9]' * i}/*.xyz"))[::-1]
597
+ for i in range(6, 0, -1)],
598
+ []
599
+ )
600
+ for m, file in enumerate(file_list[1:-1]):
601
+ sample = []
602
+ tmp_geometry_list, element_list, _ = xyz2list(file, None)
603
+ for j in range(len(element_list)):
604
+ sample.append(f"{element_list[j]:2} {float(tmp_geometry_list[j][0]):>17.12f} {float(tmp_geometry_list[j][1]):>17.12f} {float(tmp_geometry_list[j][2]):>17.12f}")
605
+
606
+ with open(self.work_directory+self.NOEXT_START_FILE+"_traj.xyz","a") as w:
607
+ atom_num = len(sample)
608
+ w.write(str(atom_num)+"\n")
609
+ w.write("Frame "+str(m)+"\n")
610
+ for i in sample:
611
+ if "\n" == i or "" == i:
612
+ continue
613
+ w.write(i+"\n")
614
+ print("\ngeometry collection was completed...\n")
615
+ return
616
+
617
+ def argrelextrema_txt_save(self, save_list, name, min_max):
618
+ NUM_LIST = [i for i in range(len(save_list))]
619
+ if min_max == "max":
620
+ local_max_energy_list_index = argrelextrema(np.array(save_list), np.greater)
621
+ with open(self.work_directory+name+".txt","w") as f:
622
+ for j in local_max_energy_list_index[0].tolist():
623
+ f.write(str(NUM_LIST[j])+"\n")
624
+ elif min_max == "min":
625
+ inverse_energy_list = (-1)*np.array(save_list, dtype="float64")
626
+ local_min_energy_list_index = argrelextrema(np.array(inverse_energy_list), np.greater)
627
+ with open(self.work_directory+name+".txt","w") as f:
628
+ for j in local_min_energy_list_index[0].tolist():
629
+ f.write(str(NUM_LIST[j])+"\n")
630
+ else:
631
+ print("error")
632
+
633
+ return
634
+
635
+
636
+
637
+ def write_xyz_file(element_list, coords, file_path, comment="save"):# element_list: list of element symbols, coords: np.array of coordinates (ang.)
638
+ with open(file_path, 'w') as f:
639
+ f.write(str(len(element_list)) + '\n')
640
+ f.write(comment+'\n')
641
+ for i in range(len(element_list)):
642
+ f.write(element_list[i] + ' ' + ' '.join([str(j) for j in coords[i]]) + '\n')
643
+ return
644
+
645
+ def make_workspace(directory):
646
+ if not os.path.exists(directory):
647
+ os.makedirs(directory)
648
+ return directory
649
+
650
+
651
+ def stack_path(directory):
652
+ file_list = glob.glob(directory + '/*_[0-9].xyz') + glob.glob(directory + '/*_[0-9][0-9].xyz') + glob.glob(directory + '/*_[0-9][0-9][0-9].xyz') + glob.glob(directory + '/*_[0-9][0-9][0-9][0-9].xyz') + glob.glob(directory + '/*_[0-9][0-9][0-9][0-9][0-9].xyz') + glob.glob(directory + '/*_[0-9][0-9][0-9][0-9][0-9][0-9].xyz') + glob.glob(directory + '/*_[0-9][0-9][0-9][0-9][0-9][0-9][0-9].xyz')
653
+
654
+ with open(directory + '/path.xyz', 'w') as f:
655
+ for file in file_list:
656
+ with open(file, 'r') as g:
657
+ lines = g.read().splitlines()
658
+ for line in lines:
659
+ f.write(line + '\n')
660
+ return