mlatom 2.0.1__py3.7.egg

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 (67) hide show
  1. EGG-INFO/PKG-INFO +145 -0
  2. EGG-INFO/SOURCES.txt +40 -0
  3. EGG-INFO/dependency_links.txt +1 -0
  4. EGG-INFO/native_libs.txt +1 -0
  5. EGG-INFO/not-zip-safe +1 -0
  6. EGG-INFO/requires.txt +2 -0
  7. EGG-INFO/scripts/MLatomF +12 -0
  8. EGG-INFO/scripts/mlatom +12 -0
  9. EGG-INFO/top_level.txt +1 -0
  10. MLatom/ML_NEA.py +2697 -0
  11. MLatom/MLatom.py +151 -0
  12. MLatom/MLatomF +0 -0
  13. MLatom/MLtasks.py +784 -0
  14. MLatom/__init__.py +0 -0
  15. MLatom/__pycache__/ML_NEA.cpython-37.pyc +0 -0
  16. MLatom/__pycache__/MLatom.cpython-37.pyc +0 -0
  17. MLatom/__pycache__/MLtasks.cpython-37.pyc +0 -0
  18. MLatom/__pycache__/__init__.cpython-37.pyc +0 -0
  19. MLatom/__pycache__/args_class.cpython-37.pyc +0 -0
  20. MLatom/__pycache__/doc.cpython-37.pyc +0 -0
  21. MLatom/__pycache__/header.cpython-37.pyc +0 -0
  22. MLatom/__pycache__/interface_MLatomF.cpython-37.pyc +0 -0
  23. MLatom/__pycache__/sliceData.cpython-37.pyc +0 -0
  24. MLatom/__pycache__/stopper.cpython-37.pyc +0 -0
  25. MLatom/args_class.py +396 -0
  26. MLatom/cs.so +0 -0
  27. MLatom/doc.py +620 -0
  28. MLatom/header.py +64 -0
  29. MLatom/interface_MLatomF.py +87 -0
  30. MLatom/interfaces/DeePMDkit/DP_inference.py +20 -0
  31. MLatom/interfaces/DeePMDkit/__init__.py +0 -0
  32. MLatom/interfaces/DeePMDkit/__pycache__/DP_inference.cpython-37.pyc +0 -0
  33. MLatom/interfaces/DeePMDkit/__pycache__/__init__.cpython-37.pyc +0 -0
  34. MLatom/interfaces/DeePMDkit/__pycache__/interface_DeePMDkit.cpython-37.pyc +0 -0
  35. MLatom/interfaces/DeePMDkit/interface_DeePMDkit.py +327 -0
  36. MLatom/interfaces/DeePMDkit/template.json +70 -0
  37. MLatom/interfaces/GAP/__init__.py +0 -0
  38. MLatom/interfaces/GAP/__pycache__/__init__.cpython-37.pyc +0 -0
  39. MLatom/interfaces/GAP/__pycache__/interface_GAP.cpython-37.pyc +0 -0
  40. MLatom/interfaces/GAP/interface_GAP.py +415 -0
  41. MLatom/interfaces/PhysNet/PhysNet_predict.py +180 -0
  42. MLatom/interfaces/PhysNet/PhysNet_train.py +425 -0
  43. MLatom/interfaces/PhysNet/__init__.py +0 -0
  44. MLatom/interfaces/PhysNet/__pycache__/PhysNet_predict.cpython-37.pyc +0 -0
  45. MLatom/interfaces/PhysNet/__pycache__/PhysNet_train.cpython-37.pyc +0 -0
  46. MLatom/interfaces/PhysNet/__pycache__/__init__.cpython-37.pyc +0 -0
  47. MLatom/interfaces/PhysNet/__pycache__/interface_PhysNet.cpython-37.pyc +0 -0
  48. MLatom/interfaces/PhysNet/interface_PhysNet.py +270 -0
  49. MLatom/interfaces/TorchANI/TorchANI_predict.py +82 -0
  50. MLatom/interfaces/TorchANI/TorchANI_train.py +217 -0
  51. MLatom/interfaces/TorchANI/__init__.py +0 -0
  52. MLatom/interfaces/TorchANI/__pycache__/TorchANI_predict.cpython-37.pyc +0 -0
  53. MLatom/interfaces/TorchANI/__pycache__/TorchANI_train.cpython-37.pyc +0 -0
  54. MLatom/interfaces/TorchANI/__pycache__/__init__.cpython-37.pyc +0 -0
  55. MLatom/interfaces/TorchANI/__pycache__/interface_TorchANI.cpython-37.pyc +0 -0
  56. MLatom/interfaces/TorchANI/interface_TorchANI.py +323 -0
  57. MLatom/interfaces/__init__.py +0 -0
  58. MLatom/interfaces/__pycache__/__init__.cpython-37.pyc +0 -0
  59. MLatom/interfaces/sGDML/__init__.py +0 -0
  60. MLatom/interfaces/sGDML/__pycache__/__init__.cpython-37.pyc +0 -0
  61. MLatom/interfaces/sGDML/__pycache__/interface_sGDML.cpython-37.pyc +0 -0
  62. MLatom/interfaces/sGDML/__pycache__/sgdml_dataset_from_extxyz.cpython-37.pyc +0 -0
  63. MLatom/interfaces/sGDML/interface_sGDML.py +276 -0
  64. MLatom/interfaces/sGDML/sgdml_dataset_from_extxyz.py +199 -0
  65. MLatom/ref.json +23 -0
  66. MLatom/sliceData.py +334 -0
  67. MLatom/stopper.py +17 -0
MLatom/ML_NEA.py ADDED
@@ -0,0 +1,2697 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import random
4
+ import time
5
+ from typing import Callable, Iterable, List, Optional, Tuple, Union, Any
6
+ import inspect
7
+ import os
8
+ import sys
9
+ import math
10
+ from subprocess import getstatusoutput, Popen, PIPE
11
+ import multiprocessing as mp
12
+ from contextlib import redirect_stdout
13
+ from ctypes import c_double, CDLL, c_long
14
+ from functools import wraps, reduce
15
+ from itertools import product
16
+ import json
17
+ import re
18
+ from io import StringIO
19
+ import argparse
20
+ try:
21
+ import matplotlib.pyplot as plt
22
+ except:
23
+ pass
24
+ ''' version: 1.5 script author: Bao-Xin Xue Xiamen University
25
+ ┌────────────────────────────────────────────────────────────────────────────────────┐
26
+ │ │
27
+ │ ┌────────────────────────────────┐ ┌────────────────────────────────┐ │
28
+ │ │0. equilibrium and velocity calc│ │ 1. user defined multiple xyz │ │
29
+ │ └────────────────────────────────┘ └────────────────────────────────┘ │
30
+ │ │ │ │
31
+ │ │ │ │
32
+ │ Newton-X MD calc │ │
33
+ │ │ │ │
34
+ │ ▼ ▼ │
35
+ │ ┌────────────────────────────────┐ ┌────────────────────────────────┐ │
36
+ │ │ gaussian calc N conformation │ │ gaussian calc N conformation │ │
37
+ │ └────────────────────────────────┘ └────────────────────────────────┘ │
38
+ │ │ │ │
39
+ │ │ │ │
40
+ │ │ │ │
41
+ │ │ │ │
42
+ │ │ │ │
43
+ │ │ ┌────────────────────────────────┐ │ │
44
+ │ └───▶│ 2. gaussian output file │◀────┘ │
45
+ │ └────────────────────────────────┘ │
46
+ │ │ │
47
+ │ │ │
48
+ │ ▼ │
49
+ │ ┌────────────────────────────────┐ │
50
+ │ │ 3. extract E and f data │ │
51
+ │ └────────────────────────────────┘ │
52
+ │ │ │
53
+ │ │ │
54
+ │ ▼ │
55
+ │ ┌────────────────────────────────┐ │
56
+ │ │ calculate cross section │ │
57
+ │ └────────────────────────────────┘ │
58
+ │ │ │
59
+ │ │ │
60
+ │ ▼ │
61
+ │ ┌────────────────────────────────┐ │
62
+ │ │ draw absorption figure │ │
63
+ │ └────────────────────────────────┘ │
64
+ └────────────────────────────────────────────────────────────────────────────────────┘
65
+ '''
66
+ # environment
67
+ # todo: define the path of MLatom and starting working directory
68
+
69
+ # Typing
70
+ _Str_or_Float = Union[str, float]
71
+ _Int_or_Float = Union[int, float]
72
+
73
+ # python script path
74
+ py_script_path = ''
75
+
76
+ class Error(Exception):
77
+ def __init__(self):
78
+ # todo: to define various error
79
+ pass
80
+
81
+ def raise_Error(self, err_pmt: str, err_code: int=1):
82
+ # todo: to raise customed error
83
+ # todo: to invoke stopper
84
+ pass
85
+
86
+ def stopper(self, stop_location: str='', err_code: int=1):
87
+ print(f'program exit at {stop_location}')
88
+ sys.exit(err_code)
89
+ pass
90
+
91
+
92
+ def err_print(content: str): # need to improve
93
+ print('--- ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ---')
94
+ print(content)
95
+ print('--- ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ERROR ---')
96
+
97
+ def warning_print(content: str):
98
+ print(f'!!!!! {content} !!!!!')
99
+
100
+ # todo: complete the typing of logger decorator
101
+ def logger(log_content: str, logfile: str='out.log') -> Callable[[Any], Any]:
102
+ def logging_decorator(func: Callable[[Any], Any]) -> Callable[[Any], Any]:
103
+ @wraps(func)
104
+ def wrapped_function(*args: Any, **kwargs: Any) -> Any:
105
+ log_string = '\n\n' + '=' * 90 + '\n'
106
+ log_string += f'{log_content} ( {func.__name__} ) started at {time.ctime()} {time.tzname[0]}'
107
+ print(log_string)
108
+
109
+ start_time = time.time()
110
+ result: Any = func(*args, **kwargs)
111
+ end_time = time.time()
112
+
113
+ spend_time = end_time - start_time
114
+ log_string = f'{log_content} ( {func.__name__} ) finished at {time.ctime()} {time.tzname[0]} |||| total spent {spend_time:.2f} sec'
115
+ log_string += '\n' + '=' * 90 + '\n\n'
116
+ print(log_string)
117
+
118
+ return result
119
+ return wrapped_function
120
+ return logging_decorator
121
+
122
+
123
+ class LinuxInterface():
124
+ def __init__(self):
125
+ pass
126
+
127
+ # todo: to finish the bash brdige with sub_process
128
+ @classmethod
129
+ def exec(cls, cmd: str) -> Tuple[int, str]:
130
+ # todo: replace `os.system` with subprocess
131
+ return_code, output = getstatusoutput(cmd)
132
+ return return_code, output
133
+
134
+ @classmethod
135
+ def cd(cls, path: str) -> int:
136
+ try:
137
+ os.chdir(path)
138
+ except:
139
+ pass
140
+ err_code = 1
141
+ else:
142
+ err_code = 0
143
+ return err_code
144
+
145
+ @classmethod
146
+ def rm(cls, path: str) -> int:
147
+ err_code, _ = cls.exec(f'rm -rf {path}')
148
+ # todo: solve with the exit code is 0
149
+ if err_code != 0:
150
+ pass
151
+ return err_code
152
+
153
+ @classmethod
154
+ def mv(cls, from_obj: str, to: str) -> int:
155
+ err_code, _ = cls.exec(f'mv {from_obj} {to}')
156
+ # todo: solve with the exit code is 0
157
+ if err_code != 0:
158
+ pass
159
+ return err_code
160
+
161
+ @classmethod
162
+ def mkdir(cls, path: str) -> int:
163
+ err_code, _ = cls.exec(f'mkdir {path}')
164
+ # todo: solve with the exit code is 0
165
+ if err_code != 0:
166
+ pass
167
+ return err_code
168
+
169
+ @classmethod
170
+ def cp(cls, from_obj: str, to: str) -> int:
171
+ # todo 1: need to consider the situation that copying a folder
172
+ err_code, _ = cls.exec(f'cp {from_obj} {to}')
173
+ # todo: solve with the exit code is not 0
174
+ if err_code != 0:
175
+ pass
176
+ return err_code
177
+
178
+ @classmethod
179
+ def ln(cls, orig: str, link: str) -> int:
180
+ err_code, _ = cls.exec(f'ln -s {orig} {link}')
181
+ # todo: solve with the exit code is not 0
182
+ if err_code != 0:
183
+ pass
184
+ return err_code
185
+
186
+ @classmethod
187
+ def new_dir(cls, path: str) -> int:
188
+ if cls.exist_path(path):
189
+ cls.rm(path)
190
+ err_code = cls.mkdir(path)
191
+ # todo: solve with the exit code is not 0
192
+ if err_code != 0:
193
+ pass
194
+ return err_code
195
+
196
+ @classmethod
197
+ def exist_path(cls, path: str) -> bool:
198
+ if os.path.exists(path):
199
+ return True
200
+ return False
201
+
202
+ @classmethod
203
+ def check_path_or_exit(cls, path: str):
204
+ if not cls.exist_path(path):
205
+ print(f'!!!!!"{path}" not exists, program exit!!!!!')
206
+ exit()
207
+
208
+ def write_file(self, content: str, file_name: str) -> bool:
209
+ try:
210
+ with open(file_name, 'w') as f:
211
+ f.write(content)
212
+ return True
213
+ # todo 3: defined error on exception
214
+ except:
215
+ # todo 4: raise file write error
216
+ return False
217
+ @staticmethod
218
+ def print_warning(content: str):
219
+ print('*' * 80)
220
+ print(content)
221
+ print('*' * 80)
222
+
223
+
224
+ class XYZ():
225
+ def __init__(self, x: _Str_or_Float, y: _Str_or_Float, z: _Str_or_Float):
226
+ self.x = float(x)
227
+ self.y = float(y)
228
+ self.z = float(z)
229
+
230
+ def __getitem__(self, key: int) -> float:
231
+ if key == 0:
232
+ return self.x
233
+ if key == 1:
234
+ return self.y
235
+ if key == 2:
236
+ return self.z
237
+ else:
238
+ return 0.0
239
+
240
+ def __sub__(self, other: 'XYZ') -> 'XYZ':
241
+ return self.__class__(other.x - self.x, other.y - self.y, other.z - self.z)
242
+ # reutrn ( (self.x - other.x)**2 +
243
+ # (self.y - other.y)**2 +
244
+ # (self.z - other.z)**2 )**0.5
245
+
246
+ def __mul__(self, other: 'XYZ') -> float:
247
+ return self.x * other.x + self.y * other.y + self.z * other.z
248
+
249
+ def mod(self) -> float:
250
+ return (self.x**2 + self.y**2 + self.z**2)**0.5
251
+
252
+
253
+ class Atom(XYZ):
254
+ def __init__(self, x:_Str_or_Float, y: _Str_or_Float, z: _Str_or_Float, label: str='', au_unit: bool=False):
255
+ self.label = label
256
+ # keep all the unit is angstrom
257
+ if au_unit:
258
+ self.x = self.au2ang(float(x))
259
+ self.y = self.au2ang(float(y))
260
+ self.z = self.au2ang(float(z))
261
+ else:
262
+ self.x = float(x)
263
+ self.y = float(y)
264
+ self.z = float(z)
265
+ # todo 3: to transfer element index into element label
266
+
267
+ @classmethod
268
+ def ang2au(cls, value: float) -> float:
269
+ return 1.8897261339 * value
270
+
271
+ @classmethod
272
+ def au2ang(cls, value: float) -> float:
273
+ return 0.5291772083 * value
274
+
275
+ def get_au_xyz(self) -> Tuple[float, float, float]:
276
+ return self.ang2au(self.x), self.ang2au(self.y), self.ang2au(self.z)
277
+
278
+ def get_ang_xyz(self) -> Tuple[float, float, float]:
279
+ return self.x, self.y, self.z
280
+
281
+ @classmethod
282
+ def get_atom_instance(cls, line_str: str, au_unit: bool=False) -> 'Atom':
283
+ label, x, y, z = line_str.split()
284
+ # todo 2: to judge whether label is a element number, and then transfer it
285
+ return Atom(x, y, z, label)
286
+
287
+ def get_atom_line_expr(self):
288
+ return f'{self.label}\t{self.x}\t{self.y}\t{self.z}\n'
289
+
290
+
291
+ class Molecule():
292
+ def __init__(self):
293
+ self.atoms: List['Atom'] = []
294
+
295
+ def read_xyz(self, file_path: str, au_unit: bool=False):
296
+ with open(file_path, 'r') as f:
297
+ data = f.read().splitlines()
298
+ try:
299
+ atom_num: int = int(data[0].strip())
300
+ except:
301
+ # todo: to raise atom number error Exception
302
+ atom_num = 0
303
+ pass
304
+
305
+ # todo: to detect whether 2nd line is empty
306
+ for line in data[2:]:
307
+ try:
308
+ label, x, y, z = line.split()
309
+ self.atoms.append(Atom(x, y, z, label))
310
+ except:
311
+ # todo: raise data number not meet the required format
312
+ pass
313
+
314
+ if atom_num != self.atom_num:
315
+ # todo 2: raise xyz atom number error problem
316
+ pass
317
+
318
+ @staticmethod
319
+ def read_element_label(input_file: str, idx: int=1) -> List[str]:
320
+ idx -= 1
321
+ with open(input_file) as f:
322
+ data = f.readlines()
323
+ return [line.split()[idx] for line in data]
324
+
325
+ @classmethod
326
+ def read_multi_xyz(cls, file_path: str, au_unit: bool=False) -> List['Molecule']:
327
+ with open(file_path, 'r') as f:
328
+ data = f.read().splitlines()
329
+ try:
330
+ atom_num = int(data[0].strip())
331
+ except:
332
+ # todo: to raise atom error Exception
333
+ atom_num = 0
334
+ pass
335
+ molecules: List[Molecule] = []
336
+ inverval = atom_num + 2
337
+ for atom_idx in range(len(data) // inverval):
338
+ mole = Molecule()
339
+ for line in data[2 + atom_idx * inverval: (atom_idx + 1) * inverval]:
340
+ atom = Atom.get_atom_instance(line)
341
+ mole.atoms.append(atom)
342
+ molecules.append(mole)
343
+ return molecules
344
+
345
+ def gaussian_opt_input(self, functionals: str='CAM-B3LYP', basis: str='def2tzvp',
346
+ cores: int=16, memory: int=16,
347
+ charge: int=0, multiplicity: int=1) -> str:
348
+ inp = f'%nproc={cores}\n'
349
+ inp += f'%mem={memory}gb\n'
350
+ inp += f'# {functionals}/{basis} opt freq\n\n'
351
+ inp += 'MLatom atuo molecule opt and freq\n\n'
352
+ inp += f'{charge} {multiplicity}\n'
353
+ for atom in self.atoms:
354
+ inp += f'{atom.label}\t{atom.x}\t{atom.y}\t{atom.z}\n'
355
+ inp += '\n'
356
+ return inp
357
+
358
+ def gaussian_calc_inp(self, functionals: str='CAM-B3LYP', basis: str='def2tzvp',
359
+ cores: int=16, memory: int=16, nStates: int=10,
360
+ charge: int=0, multiplicity: int=1) -> str:
361
+ inp = f'%nproc={cores}\n'
362
+ inp += f'%mem={memory}gb\n'
363
+ inp += f'# {functionals}/{basis} TD(nStates={nStates})\n\n'
364
+ inp += 'MLatom atuo molecule TD calculation\n\n'
365
+ inp += f'{charge} {multiplicity}\n'
366
+ for atom in self.atoms:
367
+ inp += f'{atom.label}\t{atom.x}\t{atom.y}\t{atom.z}\n'
368
+ inp += '\n'
369
+ return inp
370
+
371
+ @property
372
+ def atom_num(self):
373
+ return len(self.atoms)
374
+
375
+ def print_xyz(self, out_file: str):
376
+ with open(out_file, 'w') as f:
377
+ f.write(f'{self.atom_num}\n\n')
378
+ for atom in self.atoms:
379
+ f.write(f'{atom.label}\t{atom.x}\t{atom.y}\t{atom.z}\n')
380
+
381
+ @staticmethod
382
+ def multi_print_xyz(multi_xyz: List['Molecule'], out_file_path: str, print_num: int=0):
383
+ with open(out_file_path, 'w') as f:
384
+ if print_num:
385
+ for i in range(print_num):
386
+ mole = multi_xyz[i]
387
+ f.write(f'{mole.atom_num}\n\n')
388
+ for atom in mole.atoms:
389
+ f.write(f'{atom.label}\t{atom.x}\t{atom.y}\t{atom.z}\n')
390
+ else:
391
+ for mole in multi_xyz:
392
+ f.write(f'{mole.atom_num}\n\n')
393
+ for atom in mole.atoms:
394
+ f.write(f'{atom.label}\t{atom.x}\t{atom.y}\t{atom.z}\n')
395
+
396
+
397
+ class Plot():
398
+ def __init__(self) -> None:
399
+ plt.switch_backend('agg')
400
+ self.fig, self.ax = plt.subplots()
401
+ self.data: List[List[float]] = []
402
+ self.xlabel: str = ''
403
+ self.ylabel: str = ''
404
+ self.title: str = ''
405
+ self.grid: bool = False
406
+
407
+ def set_info(self, xlabel: str='', ylabel: str='', title: str='', grid: bool=False):
408
+ self.xlabel = xlabel
409
+ self.ylabel = ylabel
410
+ self.title = title
411
+ self.grid = grid
412
+
413
+ def plot_file(self, file: str, out_img: str='fig.png', x_column: int=1, y_column: int=2):
414
+ if not EnvCheck.check_matplotlib():
415
+ return
416
+ self.read_data(file)
417
+ x = self.column_data(x_column)
418
+ y = self.column_data(y_column)
419
+ self.ax.plot(x, y)
420
+ self.ax.set(xlabel=self.xlabel, ylabel=self.ylabel, title=self.title)
421
+ if self.grid:
422
+ self.ax.grid()
423
+ self.fig.savefig(out_img)
424
+
425
+ def plot_multiple_file(self, files: List[str], labels: List[str], colors: Optional[List[str]]=None, x_column: int=1, y_column: int=2):
426
+ if not EnvCheck.check_matplotlib():
427
+ return
428
+ plt.rcParams['legend.fontsize'] = 'x-large'
429
+ plt.tick_params(labelsize=14)
430
+ plt.subplots_adjust(left=0.16, bottom=0.15, top=0.95, right=0.95)
431
+ # plt.legend(frameon=False)
432
+ # plt.rcParams['xtick.labelsize'] = 'x-large'
433
+ # plt.rcParams['ytick.labelsize'] = 'x-large'
434
+ # plt.rcParams['axes.labelsize'] = 'x-large'
435
+ # plt.rcParams['axes.titlesize'] = 'x-large'
436
+ for idx, file in enumerate(files):
437
+ self.read_data(file)
438
+ x = self.column_data(x_column)
439
+ y = self.column_data(y_column)
440
+ if idx == 0:
441
+ if colors:
442
+ self.ax.plot(x, y, colors[idx], label=labels[idx], linewidth=3)
443
+ else:
444
+ self.ax.plot(x, y, label=labels[idx], linewidth=3)
445
+ else:
446
+ if colors:
447
+ self.ax.plot(x, y, colors[idx], label=labels[idx])
448
+ else:
449
+ self.ax.plot(x, y, label=labels[idx])
450
+ self.ax.legend(frameon=False)
451
+ # self.ax.set(xlabel=self.xlabel, ylabel=self.ylabel, title=self.title)
452
+ self.ax.set_xlabel(self.xlabel, fontsize=18)
453
+ self.ax.set_ylabel(self.ylabel, fontsize=18)
454
+ # if self.grid:
455
+ # self.ax.grid()
456
+ # self.fig.savefig(out_img_name)
457
+
458
+ def plot_spc_bar(self, x_data: List[float], y_data: List[float], width: float=0.025, color: str='g') -> None:
459
+ self.ax.bar(x_data, y_data, width=width, color=color)
460
+
461
+ def save_fig(self, fig_name: str):
462
+ if self.grid:
463
+ self.ax.grid()
464
+ self.fig.savefig(fig_name)
465
+
466
+ def column_data(self, i: int) -> List[float]:
467
+ result = []
468
+ for line in self.data:
469
+ result.append(line[i - 1])
470
+ return result
471
+
472
+ def read_data(self, file: str, drop_1st_line: bool=True, sep: str=''):
473
+ with open(file) as f:
474
+ data = f.read().splitlines()
475
+ data_list = []
476
+ if drop_1st_line:
477
+ data = data[1:]
478
+ for line in data:
479
+ if sep.strip():
480
+ data_list.append(self.trans_to_float(line.split(sep)))
481
+ else:
482
+ data_list.append(self.trans_to_float(line.split()))
483
+ self.data = data_list
484
+
485
+ @staticmethod
486
+ def trans_to_float(inp: List[str]) -> List[float]:
487
+ return [ float(x) for x in inp ]
488
+
489
+
490
+ class GaussCalc(LinuxInterface):
491
+ def __init__(self, program_path: str, work_path: str):
492
+ self.program_path = program_path
493
+ self.work_path = work_path
494
+ self.temp_path = os.path.join(work_path, 'TEMP')
495
+ self.gauss_path = os.path.join(work_path, 'GAUSS')
496
+ self.all_com_path = os.path.join(self.gauss_path, 'all_com')
497
+ self.all_log_path = os.path.join(self.gauss_path, 'all_log')
498
+ self.gauss_data_path = os.path.join(self.gauss_path, 'data')
499
+ self.path_check()
500
+ # todo 5: check gaussian environment
501
+ self.calc_flag = False
502
+ # extract xyz file needed data
503
+ self.element = ['X', 'H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Uut', 'Fl', 'Uup', 'Lv', 'Uus',' Uuo']
504
+ self.incre = 5
505
+ self.sep = '-' * 69
506
+ self.colums = [1, 3, 4, 5]
507
+ self.header = ['Standard orient', 'Z-matrix orient', 'Input orient']
508
+ self.inp_pre_xyz: str = ''
509
+ self.inp_after_xyz: str = ''
510
+ self.gauss: str = EnvCheck.check_gaussian_version()
511
+
512
+ def path_check(self):
513
+ # check the TEMP directory is new
514
+ self.cd(self.work_path)
515
+ if self.exist_path(self.temp_path):
516
+ self.__class__.rm(self.temp_path)
517
+ self.__class__.mkdir(self.temp_path)
518
+ # check the GAUSS directory is new
519
+ if self.exist_path(self.gauss_path):
520
+ self.__class__.rm(self.gauss_path)
521
+ self.__class__.mkdir(self.gauss_path)
522
+ self.__class__.mkdir(self.all_com_path)
523
+ self.__class__.mkdir(self.all_log_path)
524
+ self.__class__.mkdir(self.gauss_data_path)
525
+
526
+ @logger('optimize and calc molecule freq')
527
+ def gaussain_opt(self, molecule: Molecule, functionals: str='CAM-B3LYP', basis: str='def2tzvp',
528
+ cores: int=16, memory: int=16,
529
+ charge: int=0, multiplicity: int=1):
530
+ # we assume that the current working directory is at $root/TEMP
531
+ self.__class__.cd(self.temp_path)
532
+ inp = molecule.gaussian_opt_input(functionals, basis, cores, memory, charge, multiplicity)
533
+ with open(os.path.join(self.temp_path, 'opt.com'), 'w') as f:
534
+ f.write(inp)
535
+ self.gaussian_opt_calc()
536
+
537
+ def gaussian_opt_calc(self, file_name: str='opt.com'):
538
+ if not file_name.endswith('.com'):
539
+ err_print('gauss input file name not ends with .com')
540
+ exit()
541
+ self.cp(file_name, self.temp_path)
542
+ cwd = os.getcwd()
543
+ self.cd(self.temp_path)
544
+ if self.gauss == '':
545
+ cls.print_warning('Gaussian 09 or Gaussian 16 not exists!!!!! If you use old version of Gaussian ,please update it!!!!!')
546
+ exit()
547
+ err_code, out = self.__class__.exec(f'{self.gauss} {file_name}')
548
+ if err_code != 0 or out != '':
549
+ err_print(f'Gaussian calculation failed!!!\nstatus code: {err_code} (0 stands for normal exit, none 0 stands for error)\noutput: {out}')
550
+ gauss_name_pre = file_name[:-3]
551
+ self.mv(f'{gauss_name_pre}*', self.gauss_path)
552
+ self.cd(cwd)
553
+
554
+ def gaussain_TD_with_para(self, idx: int, molecule: Molecule, functionals: str='CAM-B3LYP', basis: str='def2svp',
555
+ cores: int=16, memory: int=16, nStates: int=10,
556
+ charge: int=0, multiplicity: int=1):
557
+ # we assume that the current working directory is at $root/TEMP
558
+ inp = molecule.gaussian_calc_inp(functionals, basis, cores, memory, nStates, charge, multiplicity)
559
+ self.gaussain_TD_calc(idx, inp)
560
+
561
+ def gaussian_TD_calc_with_template(self, idx: int, molecule: Molecule):
562
+ inp = self.inp_pre_xyz # inp_pre_xyz has included an '\n'
563
+ for atom in molecule.atoms:
564
+ inp += atom.get_atom_line_expr()
565
+ inp += self.inp_after_xyz + '\n'
566
+ self.gaussain_TD_calc(idx, inp)
567
+
568
+ def gaussain_TD_calc(self, idx: int, inp: str):
569
+ orig_path = os.getcwd()
570
+ self.cd(self.temp_path)
571
+ if self.gauss == '':
572
+ cls.print_warning('Gaussian 09 or Gaussian 16 not exists!!!!! If you use old version of Gaussian ,please update it!!!!!')
573
+ exit()
574
+ with open(os.path.join(self.temp_path, f'{idx}.com'), 'w') as f:
575
+ f.write(inp)
576
+ err_code, out = self.__class__.exec(f'{self.gauss} {idx}.com')
577
+ if err_code != 0 or out != '':
578
+ err_print(f'Gaussian calculation failed!!!\nstatus code: {err_code} (0 stands for normal exit, none 0 stands for error)\noutput: {out}')
579
+ self.__class__.mv(f'{idx}.com', self.all_com_path)
580
+ self.__class__.mv(f'{idx}.log', self.all_log_path)
581
+ self.cd(orig_path)
582
+
583
+ @staticmethod
584
+ def check_opt_freq_inp(path: str) -> bool:
585
+ with open(path) as f:
586
+ data = f.read().splitlines()
587
+ for line in data:
588
+ parts = re.split('[ /]', line.strip())
589
+ try:
590
+ if parts[0][0] == '#':
591
+ for each in parts:
592
+ if each.lower() == 'freq':
593
+ return True
594
+ except:
595
+ pass
596
+ return False
597
+
598
+ @staticmethod
599
+ def check_TD_inp(path: str) -> bool:
600
+ with open(path) as f:
601
+ data = f.read().splitlines()
602
+ for line in data:
603
+ parts = re.split('[ /]', line.strip())
604
+ try:
605
+ if parts[0][0] == '#':
606
+ for each in parts:
607
+ if each.lower() == 'td':
608
+ return True
609
+ except:
610
+ pass
611
+ return False
612
+
613
+ def parse_gauss_inp_file(self, path: str) -> bool:
614
+ def check_charge_multi(line: str):
615
+ splited = line.strip().split()
616
+ if len(splited) == 2:
617
+ try:
618
+ charge, multiplicity = int(splited[0]), int(splited[1])
619
+ return True
620
+ except:
621
+ pass
622
+ return False
623
+
624
+ def check_geom(line: str):
625
+ splited = line.strip().split()
626
+ element = ['H', 'He', 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar', 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe', 'Cs', 'Ba', 'La', 'Ce', 'Pr', 'Nd', 'Pm', 'Sm', 'Eu', 'Gd', 'Tb', 'Dy', 'Ho', 'Er', 'Tm', 'Yb', 'Lu', 'Hf', 'Ta', 'W', 'Re', 'Os', 'Ir', 'Pt', 'Au', 'Hg', 'Tl', 'Pb', 'Bi', 'Po', 'At', 'Rn', 'Fr', 'Ra', 'Ac', 'Th', 'Pa', 'U', 'Np', 'Pu', 'Am', 'Cm', 'Bk', 'Cf', 'Es', 'Fm', 'Md', 'No', 'Lr', 'Rf', 'Db', 'Sg', 'Bh', 'Hs', 'Mt', 'Ds', 'Rg', 'Cn', 'Uut', 'Fl', 'Uup', 'Lv', 'Uus',' Uuo']
627
+ ele_little = list(map(lambda x: x.lower(), element))
628
+ if len(splited) == 4:
629
+ try:
630
+ label, x, y, z = splited
631
+ x = float(x); y = float(y); z=float(z)
632
+ if label.lower() in ele_little:
633
+ return True
634
+ label_num = int(label)
635
+ if label_num < 100:
636
+ return True
637
+ except:
638
+ pass
639
+ return False
640
+
641
+ with open(path) as f:
642
+ data = f.read().splitlines()
643
+ flag_charge_multi = False
644
+ # flag_xyz_finished = False
645
+ pre_xyz = ''
646
+ # after_xyz = ''
647
+ idx_not_geom = 0
648
+ for idx, line in enumerate(data):
649
+ if flag_charge_multi and not check_geom(line):
650
+ # flag_xyz_finished = True
651
+ idx_not_geom = idx
652
+ # after_xyz += line + '\n'
653
+ break
654
+ if check_charge_multi(line) and check_geom(data[idx + 1]):
655
+ flag_charge_multi = True
656
+ pre_xyz += line + '\n'
657
+ continue
658
+ # if flag_xyz_finished:
659
+ # after_xyz += line + '\n'
660
+ # if not flag_charge_multi and not flag_xyz_finished:
661
+ if not flag_charge_multi:
662
+ pre_xyz += line + '\n'
663
+ if flag_charge_multi:
664
+ self.inp_pre_xyz = pre_xyz
665
+ self.inp_after_xyz = '\n'.join(data[idx_not_geom:])
666
+ return flag_charge_multi
667
+
668
+ def extract_xyz(self, log_file: str) -> 'Molecule':
669
+ idx = 0
670
+ start = 0
671
+ end = 0
672
+ with open(log_file) as f:
673
+ geom = []
674
+ for line in f:
675
+ idx += 1
676
+ # if line.strip() in header:
677
+ if reduce(lambda x, y: x or y, map(lambda x: x in line.strip(), self.header)):
678
+ start = idx + self.incre
679
+ end = start
680
+ geom = []
681
+ if start <= idx <= end:
682
+ if line.strip() == self.sep:
683
+ end = idx - 1
684
+ else:
685
+ end += 1
686
+ splited = line.strip().split()
687
+ geom.append([ splited[x] for x in self.colums ])
688
+ molecule = Molecule()
689
+ if len(geom) == 0:
690
+ # todo 3: raise error about extracted xyz file atom number is wrong
691
+ pass
692
+ else:
693
+ for atom in geom:
694
+ # todo 1: to check whether atom[0] is not a int type var
695
+ # todo 2: to check whether atom[1, 2, 3] is not a float type var
696
+ label = self.element[int(atom[0])]
697
+ x, y, z = list(map(float, atom[1:]))
698
+ molecule.atoms.append(Atom(x, y, z, label))
699
+ return molecule
700
+
701
+ def process_gaussian_log(self, folder: str, n_state: int=10, pattern: str=r'[0-9]+?\.log', regex: bool=False):
702
+ orig_folder = os.getcwd()
703
+ self.cd(folder)
704
+ if pattern == "":
705
+ empty = True
706
+ else:
707
+ empty = False
708
+ if empty:
709
+ grep_str = "''"
710
+ else:
711
+ if regex:
712
+ # use grep -E
713
+ grep_str = f"-E '{pattern}'"
714
+ pass
715
+ else:
716
+ grep_str = f"'{pattern}'"
717
+ # using common grep command
718
+ pass
719
+ cmd = '''rm %s/E*.dat %s/f*.dat # gauss_data_path
720
+ for file in `ls | grep %s | sort -n`; do # grep_str
721
+ for i in `seq 1 %d`; do # number of excited state
722
+ grep 'Excited State' $file | grep ' '$i':' | awk '{print $5}' >> %s/'E'$i.dat
723
+ grep 'Excited State' $file | grep ' '$i':' | awk '{print $9}' | sed s/f=//g >> %s/'f'$i.dat
724
+ done
725
+ done''' % (self.gauss_data_path, self.gauss_data_path, grep_str, n_state, self.gauss_data_path, self.gauss_data_path)
726
+ err_code, _ = self.__class__.exec(cmd) # todo 3: raise error on fail
727
+ # todo 3: to check whether all the E*.inp & f*.inp has the same number
728
+ if err_code != 0:
729
+ err_print(f'post gaussian data process failed with error code {err_code}')
730
+ exit()
731
+ self.cd(orig_folder)
732
+
733
+ def process_top_n_E_f(self, folder: str, max_state: int, line_num: int):
734
+ orig_folder = os.getcwd()
735
+ self.cd(folder)
736
+ cmd = '''rm %s/E*.dat %s/f*.dat # gauss_data_path
737
+ for file in `ls | sort -n | head -n %d`; do # get top n line
738
+ for i in `seq 1 %d`; do # number of excited state
739
+ grep 'Excited State' $file | grep ' '$i':' | awk '{print $5}' >> %s/'E'$i.dat
740
+ grep 'Excited State' $file | grep ' '$i':' | awk '{print $9}' | sed s/f=//g >> %s/'f'$i.dat
741
+ done
742
+ done''' % (self.gauss_data_path, self.gauss_data_path,
743
+ line_num, max_state,
744
+ self.gauss_data_path, self.gauss_data_path)
745
+ err_code, _ = self.exec(cmd)
746
+ if err_code != 0: # todo 3: raise an error
747
+ err_print(f'pre-calculated gaussian log file process failed with error code {err_code}')
748
+ exit()
749
+ self.cd(orig_folder)
750
+
751
+ def process_one_gauss_log(self, log_file: str) -> Tuple[List[float], List[float]]:
752
+ cmd = '''grep 'Excited State' %s | awk '{print $5}' > %s/SPC_E.TEMP''' % (log_file, self.temp_path)
753
+ err_code, _ = self.exec(cmd)
754
+ E_file = os.path.join(self.temp_path, 'SPC_E.TEMP')
755
+ if err_code != 0:
756
+ err_print(f'extract E value from one gaussian log failed with error code: {err_code}')
757
+
758
+ cmd = '''grep 'Excited State' %s | awk '{print $9}' | sed s/f=//g > %s/SPC_f.TEMP''' % (log_file, self.temp_path)
759
+ err_code, _ = self.exec(cmd)
760
+ f_file = os.path.join(self.temp_path, 'SPC_f.TEMP')
761
+ if err_code != 0:
762
+ err_print(f'extract f value from one gaussian log failed with error code: {err_code}')
763
+
764
+ with open(E_file) as f:
765
+ E_list = list(map(float, f.read().splitlines()))
766
+ with open(f_file) as f:
767
+ f_list = list(map(float, f.read().splitlines()))
768
+ self.rm(E_file)
769
+ self.rm(f_file)
770
+ return E_list, f_list
771
+
772
+
773
+ class NewtonXCalc(LinuxInterface):
774
+ au = 0.5291772083
775
+ def __init__(self, work_path: str) -> None:
776
+ self.work_path = work_path
777
+ self.temp_path = os.path.join(work_path, 'TEMP')
778
+ self.NX_path = os.path.join(self.work_path, 'NX')
779
+ self.path_check()
780
+ self.calc_flag = False
781
+ self.multi_xyz: List[Molecule] = []
782
+ # todo 4: check NX environment
783
+ pass
784
+
785
+ def path_check(self) -> None:
786
+ if self.exist_path(self.temp_path):
787
+ self.__class__.rm(self.temp_path)
788
+ self.__class__.mkdir(self.temp_path)
789
+ if self.exist_path(self.NX_path):
790
+ self.__class__.rm(self.NX_path)
791
+ self.__class__.mkdir(self.NX_path)
792
+
793
+ @logger('start NX calculation to get nuclear ensemble')
794
+ def invoke_NX(self, geom_path: str, freq_path: str, atom_num: int, NEA_point: int=500) -> None:
795
+ self.cd(self.NX_path)
796
+ self.change_geom(geom_path)
797
+ self.run_dynamic(freq_path, atom_num, NEA_point)
798
+ self.restore_multi_xyz(atom_num)
799
+ pass
800
+
801
+ def change_geom(self, geom_path: str) -> None:
802
+ # check whether trnasfer success (delete /dev/null and then check the output log)
803
+ self.exec(f'$NX/xyz2nx < {geom_path} &> /dev/null')
804
+
805
+ def run_dynamic_old(self, freq_file: str, atom_num: int, NEA_point: int) -> None:
806
+ self.mv(freq_file, os.path.join(self.NX_path, 'freq.out') )
807
+ nx_inp = '&dat\n nact = 2\n numat = %d\n npoints = %d\n file_geom = geom\n iprog = 4\n' % (atom_num, NEA_point) + \
808
+ ' file_nmodes = freq.out\n anh_f = 1\n temp = 0.0\n ics_flg = n\n chk_e = 0\n' + \
809
+ ' nis = 1\n nfs = 2\n kvert = 1\n de = 100\n prog = 6.5\n iseed = 0\n lvprt = 1\n /'
810
+ with open('initqp_input', 'w') as f:
811
+ f.write(nx_inp)
812
+ err_code, out = self.exec(f'$NX/initcond.pl > NX-MD.log 2>&1')
813
+ if err_code != 0 or out != '':
814
+ err_print(f'Newton-X calculation failed!!!\nstatus code: {err_code} (0 stands for normal exit, none 0 stands for error)\noutput: {out}')
815
+ # todo 3: remove NX temp folder and other uncessary folder
816
+
817
+ def run_dynamic(self, freq_file: str, atom_num: int, NEA_point: int) -> None:
818
+ inp_name = 'initqp_input'
819
+ self.mv(freq_file, os.path.join(self.NX_path, 'freq.out') )
820
+ nx_inp = '&dat\n nact = 2\n numat = %d\n npoints = %d\n file_geom = "geom"\n iprog = 4\n' % (atom_num, NEA_point) + \
821
+ ' file_nmodes = "freq.out"\n anh_f = 1\n temp = 0.0\n ics_flg = "n"\n chk_e = 0\n' + \
822
+ ' nis = 1\n nfs = 2\n kvert = 1\n de = 100\n prog = 6.5\n iseed = 1234\n lvprt = 1\n/\n'
823
+ with open(inp_name, 'w') as f:
824
+ f.write(nx_inp)
825
+ # err_code, out = self.exec(f'$NX/initcond.pl > NX-MD.log 2>&1')
826
+ err_code, out = self.exec('$NX/mk_qvector < initqp_input ; ' \
827
+ '$NX/weight < initqp_input &> weight.log ; ' \
828
+ '$NX/initqp < initqp_input &> initqp.log ')
829
+ if err_code != 0 or out != '':
830
+ err_print(f'Newton-X calculation failed!!!\nstatus code: {err_code} (0 stands for normal exit, none 0 stands for error)\noutput: {out}')
831
+
832
+ def restore_multi_xyz(self, atom_num: int):
833
+ with open('ini_qv') as f:
834
+ data = f.readlines()
835
+ elem = Molecule.read_element_label('geom')
836
+ cnt = 0
837
+ mole = Molecule()
838
+ for line in data[1:]:
839
+ tmp = line.split()
840
+ if len(tmp) == 2:
841
+ # if cnt:
842
+ self.multi_xyz.append(mole)
843
+ mole = Molecule()
844
+ cnt = 0
845
+ elif len(tmp) == 6:
846
+ x, y, z = self._process_ini_qv_line(tmp)
847
+ atom = Atom(x, y, z, elem[cnt])
848
+ cnt += 1
849
+ mole.atoms.append(atom)
850
+ else:
851
+ err_print('processing ini_qv failed! exit!')
852
+ exit()
853
+
854
+ def _process_ini_qv_line(self, splited_line: List[str]) -> List[float]:
855
+ xyz = map(lambda x: float(x.replace('D', 'e')), splited_line[0: 3])
856
+ return list(map(lambda x: self.au * x, xyz))
857
+
858
+ def restore_multi_xyz_old(self, atom_num: int):
859
+ # working directory: In self.NX_path
860
+ awk_inp = '''atom_num=%d \n''' % (atom_num) + \
861
+ r'''sed -n /Geometry/,/Velocity\ in\ NX/p final_output | sed /Velocity/d | awk '{printf ("%s\t", $1)}''' + \
862
+ r''' {printf ("%.8f\t", $3*0.5291772083)} {printf ("%.8f\t", $4*0.5291772083)} {printf ("%.8f\n", $5*0.5291772083)}' ''' + \
863
+ r''' | sed -r s/Geometry.+/$atom_num\\n/g > xyz.dat'''
864
+ err_code, out = self.exec(awk_inp)
865
+ # todo 4: even it execute fail, it will also returns 0, so we should judge it throught the output form (err_code, output)
866
+ if err_code != 0 or out != '':
867
+ err_print(f'awk process for multiple xyz file (xyz.dat) failed!!!\nstatus code: {err_code} (0 stands for normal exit, none 0 stands for error)\noutput: {out}')
868
+ # self.multi_xyz = Molecule.read_multi_xyz(os.path.join(self.work_path, 'xyz.dat'))
869
+ self.multi_xyz = Molecule.read_multi_xyz('xyz.dat')
870
+
871
+ def clean_file(self):
872
+ pass
873
+
874
+ def NX_interface(self) -> int:
875
+ err_code = 0
876
+ return err_code
877
+ pass
878
+
879
+
880
+ class LineDate():
881
+ def __init__(self, string: str) -> None:
882
+ self.string: str = string
883
+
884
+ def __iadd__(self, other: str) -> 'LineDate':
885
+ return LineDate(self.string + other + '\n')
886
+
887
+ @property
888
+ def string_one_line(self) -> str:
889
+ return self.string.replace('\n', ' ')
890
+
891
+
892
+ class ML_calc(LinuxInterface):
893
+
894
+ def __init__(self, program_path:str, work_path: str) -> None:
895
+ self.program_path: str = program_path
896
+ self.work_path: str = work_path
897
+ self.task_type: str = ''
898
+ self.inp: LineDate = LineDate('')
899
+ self.exist_python_var: bool = EnvCheck.check_python3()
900
+ self.ML_output: str = ''
901
+
902
+ def check_file_(self):
903
+ # todo 5: to check whether itrain isubtrain ivalidate itest and *.unf *.log or other MLatom is exists
904
+ pass
905
+
906
+ def var_to_str(self, obj: Any) -> str:
907
+ namespace = globals()
908
+ return [ n for n in namespace if namespace[n] is obj ] [0]
909
+
910
+ def add_inp(self, inp_vars: List[Any], forbidden: List[Any], key: str=''):
911
+ callers_local_vars = inspect.currentframe().f_back.f_locals.items()
912
+ all = set()
913
+ for _, val in callers_local_vars:
914
+ if val in forbidden:
915
+ continue
916
+ if val in all:
917
+ continue
918
+ if val not in inp_vars:
919
+ continue
920
+ else:
921
+ all.add(val)
922
+ candidates = [var_name for var_name, var_val in callers_local_vars if var_val is val]
923
+ for var_name_out in candidates:
924
+ if key:
925
+ self.inp += f'{key}={var_name_out}'
926
+ else:
927
+ self.inp += f'{var_name_out}={val}'
928
+ # print(f'variable name: {var_name_out}, variable value: {val}')
929
+
930
+ def only_one_True(self, *obj: bool) -> bool:
931
+ cnt = 0
932
+ for each in obj:
933
+ if each:
934
+ cnt += 1
935
+ if cnt == 1:
936
+ return True
937
+ else:
938
+ return False
939
+
940
+ def set_task_type(self, estAccMLmodel: bool=False, createMLmodel: bool=False, useMLmodel: bool=False):
941
+ # todo 2: to check whether it exists
942
+ check = self.only_one_True(estAccMLmodel, createMLmodel, useMLmodel)
943
+ if not check:
944
+ # todo 4: to raise an error:
945
+ pass
946
+ if estAccMLmodel:
947
+ self.inp += 'estAccMLmodel'
948
+ elif createMLmodel:
949
+ self.inp += 'createMLmodel'
950
+ elif useMLmodel:
951
+ self.inp += 'useMLmodel'
952
+ else:
953
+ # todo 3: raise multiple task error
954
+ pass
955
+
956
+ def set_index_num(self, Ntrain: _Int_or_Float=0, NSubtrain: _Int_or_Float=0, Nvalidate: _Int_or_Float=0, Ntest: _Int_or_Float=0, Nuse: int=0):
957
+ self.add_inp([Ntrain, NSubtrain, Nvalidate, Ntest, Nuse], [0])
958
+ # for each in [Ntrain, NSubtrain, Nvalidate, Ntest]:
959
+ # if each:
960
+ # self.inp += f'{self.var_to_str(each)}={each}'
961
+ # if Nuse != 0:
962
+ # self.inp += f'{self.var_to_str(Nuse)}={Nuse}'
963
+
964
+ def set_index_file(self, iTrainin: str='', iSubtrainin: str='', iValidatein: str='', iTestin: str=''):
965
+ # todo 5: sampling=user-defined
966
+ self.add_inp([iTrainin, iSubtrainin, iValidatein, iTestin], [''])
967
+ # for each in [iTrainin, iSubtrainin, iValidatein, iTestin]:
968
+ # if each != '':
969
+ # self.inp += f'{self.var_to_str(each)}={each}'
970
+
971
+ def set_data_set_inp_file(self, XfileIn: str='', Yfile: str='', XYZfile: str='', Yb: str='', Yt: str=''):
972
+ if XfileIn != '' and XYZfile != '':
973
+ # todo 4: raise a error: xfilein and xyzfile can not appear at the same time
974
+ pass
975
+ self.add_inp([XfileIn, Yfile, XYZfile, Yb, Yt], [''])
976
+ # for each in [XfileIn, Yfile, XYZfile, Yb, Yt]:
977
+ # if each != '':
978
+ # self.inp += f'{self.var_to_str(each)}={each}'
979
+
980
+ def set_kernel(self, kernel: str='', Gaussian: bool=False, Laplacian: bool=False, exponential: bool=False, Matern: bool=False):
981
+ if not self.only_one_True(Gaussian, Laplacian, exponential, Matern):
982
+ # todo 3: deal with the multiple True kernel
983
+ pass
984
+ self.add_inp([Gaussian, Laplacian, exponential, Matern], [False], key='kernel')
985
+ if Matern and kernel in ['1', '2']:
986
+ self.add_other('nn', kernel)
987
+ if not (Gaussian or Laplacian or exponential or Matern) and kernel != '':
988
+ self.add_other('kernel', kernel)
989
+ # seted = False
990
+ # for each in [Gaussian, Laplacian, exponential, Matern]:
991
+ # if each :
992
+ # self.inp += f'kernel={self.var_to_str(each)}'
993
+ # if Matern:
994
+ # if kernel in ['1', '2']:
995
+ # self.inp += f'nn={kernel}'
996
+ # seted = True
997
+ # break
998
+ # if not seted:
999
+ # self.inp += f'kernel={kernel}'
1000
+
1001
+ def set_CV_pref_inp(self, iCVtestPrefIn: str='', iCVoptPrefIn: str=''):
1002
+ self.add_inp([iCVtestPrefIn, iCVoptPrefIn], [''])
1003
+ # for each in [iCVtestPrefIn, iCVoptPrefIn]:
1004
+ # if each:
1005
+ # self.inp += f'{self.var_to_str(each)}={each}'
1006
+
1007
+ def set_model_inp(self, MLmodelIn: str=''):
1008
+ self.add_inp([MLmodelIn], [''])
1009
+ # for each in [MLmodelIn]:
1010
+ # if each:
1011
+ # self.inp += f'{self.var_to_str(each)}={each}'
1012
+
1013
+ def set_hyper_parameter(self, Lambda: _Str_or_Float=0.0, NlgLambda: int=0, lgLambdaL: int=0, lgLambdaH: int=0,
1014
+ Sigma: _Str_or_Float=0.0, NlgSigma: int=0, lgSigmaL: int=0, lgSigmaH: int=0, lgOptDepth: int=0):
1015
+ # todo 3: if sigma or lambda not equal to opt, but type is str, to raise an error
1016
+ if (type(Lambda) == str and Lambda != 'opt') or (type(Sigma) == str and Sigma != 'opt'):
1017
+ # todo 3: raise an error about input an string to hyper parameter
1018
+ pass
1019
+ self.add_inp([Lambda, Sigma, NlgLambda, lgLambdaL, lgLambdaH, NlgSigma, lgSigmaL, lgSigmaH, lgOptDepth], [0, 0.0, ''])
1020
+ # for each in [Lambda, Sigma, NlgLambda, lgLambdaL, lgLambdaH, NlgSigma, lgSigmaL, lgSigmaH]:
1021
+ # if each:
1022
+ # self.inp += f'{self.var_to_str(each)}={each}'
1023
+
1024
+ def set_index_out(self, iTrainOut: str='', iSubtrainOut: str='', iValidateOut: str='', iTestOut: str=''):
1025
+ self.add_inp([iTrainOut, iSubtrainOut, iValidateOut, iTestOut], [''])
1026
+ # for each in [iTrainOut, iSubtrainOut, iValidateOut, iTestOut]:
1027
+ # if each:
1028
+ # self.inp += f'{self.var_to_str(each)}={each}'
1029
+
1030
+ def set_est_out(self, YestFile: str='', YgradEstFile: str='', YestT: str=''):
1031
+ self.add_inp([YestFile, YgradEstFile, YestT], [''])
1032
+ # for each in [YestFile, YgradEstFile, YestT]:
1033
+ # if each:
1034
+ # self.inp += f'{self.var_to_str(each)}={each}'
1035
+
1036
+ def set_model_out(self, MLmodelOut: str=''):
1037
+ self.add_inp([MLmodelOut], [''])
1038
+ # for each in [MLmodelOut]:
1039
+ # if each:
1040
+ # self.inp += f'{self.var_to_str(each)}={each}'
1041
+
1042
+ def set_sampling(self, user_defined: bool=True):
1043
+ if user_defined:
1044
+ self.inp += 'sampling=user-defined'
1045
+
1046
+ def mix_single_file_data(self, est_file: str, orig_file: str) -> str:
1047
+ with open(est_file) as f:
1048
+ est = f.read().splitlines()
1049
+ with open(orig_file) as f:
1050
+ orig = f.read().splitlines()
1051
+ orig_num = len(orig)
1052
+ mixed_data = []
1053
+ for idx, e in enumerate(est):
1054
+ if idx > orig_num - 1:
1055
+ tmp = e if float(e) > 0.0 else '0.0'
1056
+ else: # use orig value
1057
+ tmp = orig[idx]
1058
+ mixed_data.append(tmp)
1059
+ return '\n'.join(mixed_data)
1060
+
1061
+ def mix_multiple_file(self, max_state: int, est_path: str, orig_path: str, out_path: str) -> None:
1062
+ for i in range(1, max_state + 1):
1063
+ E_string = self.mix_single_file_data(os.path.join(est_path, f'E{i}.dat'), os.path.join(orig_path, f'E{i}.dat'))
1064
+ f_string = self.mix_single_file_data(os.path.join(est_path, f'f{i}.dat'), os.path.join(orig_path, f'f{i}.dat'))
1065
+ with open(os.path.join(out_path, f'E{i}.dat'), 'w') as f:
1066
+ f.write(E_string)
1067
+ with open(os.path.join(out_path, f'f{i}.dat'), 'w') as f:
1068
+ f.write(f_string)
1069
+
1070
+
1071
+ def run_ML_shell_layer(self, work_path: str=''):
1072
+ path: str = ''
1073
+ if work_path:
1074
+ path = work_path
1075
+ else:
1076
+ path = self.work_path
1077
+ if not self.exist_path(path):
1078
+ self.mkdir(path)
1079
+ self.cd(path)
1080
+ prg = os.path.join(self.program_path, 'MLatom.py')
1081
+ # todo 1: add the exception of writing error
1082
+ with open('ml.inp', 'w') as f:
1083
+ f.write(self.inp.string)
1084
+ if self.exist_python_var:
1085
+ err_code, _ = self.exec(f'$python3 {prg} ml.inp &> ml.log')
1086
+ else:
1087
+ err_code, _ = self.exec(f'{prg} ml.inp &> ml.log')
1088
+ if err_code != 0:
1089
+ err_print('ML traiing failed with exit code %d' % err_code)
1090
+ exit()
1091
+ self.inp: LineDate = LineDate('')
1092
+ # todo 3: post data process
1093
+
1094
+ def run_ML(self, work_path: str=''):
1095
+ path = work_path if work_path else self.work_path
1096
+ if not self.exist_path(path):
1097
+ self.mkdir(path)
1098
+ self.cd(path)
1099
+ with open('ml.inp', 'w') as f:
1100
+ f.write(self.inp.string)
1101
+ s = StringIO()
1102
+ with redirect_stdout(s):
1103
+ prg = os.path.join(self.program_path, 'MLatomF')
1104
+ para = self.inp.string_one_line.split()
1105
+ with Popen([prg] + para, stdout=PIPE, stderr=PIPE) as proc:
1106
+ print(proc.stdout.read().decode('utf-8'))
1107
+ err = proc.stderr.read().decode('utf-8')
1108
+ self.ML_output = s.getvalue()
1109
+ with open('log', 'w') as f:
1110
+ f.write(self.ML_output)
1111
+ if err:
1112
+ warning_print(f'MLatom.run_ML returns a non-empty error: {err}, PAY ATTENTION to it.')
1113
+ self.inp: LineDate = LineDate('')
1114
+
1115
+ def add_other(self, para: str, value: Any):
1116
+ self.inp += f'{para}={value}'
1117
+
1118
+ def collect_validate_RMSE_shell_layer(self) -> float:
1119
+ cmd = "grep RMSE ml.log | sed -n 4p | awk '{print $3}' > RMSE.tmp"
1120
+ self.exec(cmd)
1121
+ with open('RMSE.tmp') as f:
1122
+ data = f.read().strip()
1123
+ self.rm('RMSE.tmp')
1124
+ if data == '':
1125
+ err_print('collect RMSE value failed unexpectly, exit!')
1126
+ exit()
1127
+ return float(data)
1128
+ # todo 3: transfer xyz file to x.dat file
1129
+
1130
+ def collect_validate_RMSE(self) -> float:
1131
+ find_result = re.findall('RMSE = *(.+)', self.ML_output)
1132
+ self.ML_output = ''
1133
+ try:
1134
+ return float(find_result[1])
1135
+ except:
1136
+ err_print('encountered error when extract RMSE value at MLatom output')
1137
+ exit()
1138
+
1139
+
1140
+ class CrossSection_calc(LinuxInterface):
1141
+ spec_type = 1
1142
+ init_state = 1
1143
+ final_state = [2, 3, 4]
1144
+ prob_kind = 'F'
1145
+ screen = 0
1146
+ os_condon = -1
1147
+ norm = 'local'
1148
+ seed = 0
1149
+ function = 'gauss'
1150
+ # delta = 0.01
1151
+ temp = 0
1152
+ nref = 1
1153
+ eps = 0.002
1154
+ kappa = 3
1155
+ run_IS = 0
1156
+
1157
+ BK = 0.3166813639E-5 # Boltzmann constant Hartree/kelvin
1158
+ bk_ev = 8.617343E-5 # Boltzmann constant eV/kelvin
1159
+ proton = 1822.888515 # (a.m.u.)/(electron mass)
1160
+ timeunit = 24.188843265E-3 # femtoseconds
1161
+ au2ev = 27.21138386 # au to eV
1162
+ pi = 3.141592653589793 # pi
1163
+ au2ang = 0.52917720859 # au to angstrom
1164
+ deg2rad = 1.745329252E-2 # degree to radian (pi/180)
1165
+ au2cm = 219474.625 # hartree to cm-1
1166
+ cm2au = 4.55633539E-6 # cm-1 to hartree
1167
+ h_planck = 1.5198298508E-16 # h hartree.s
1168
+ h_evs = 4.13566733E-15 # h eV.s
1169
+ light = 299792458 # speed of light m/s
1170
+ lightau = 137.035999139 # speed of light au
1171
+ e_charge = -1.602176487E-19 # electron charge C
1172
+ e_mass = 9.10938215E-31 # electron mass kg
1173
+ eps0 = 8.854187817e-12 # vacuum permitivity C^2*s^2*kg^-1*m^-3
1174
+ # cross section const
1175
+ hplanck = h_evs
1176
+ ev2nm = 1E9 * hplanck * light
1177
+ gamma = 1 # temperature = 0 ==> gamma = 1
1178
+ nref = 1 # ratio
1179
+ cs_coeff_pre = pi * e_charge**2 / (2 * e_mass * light * eps0 * nref) # m^2/s unit
1180
+ cs_coeff = cs_coeff_pre * hplanck / (2 * pi) * 1E20 # Angstrom^2*eV
1181
+
1182
+
1183
+ def __init__(self, work_path: str):
1184
+ self.data_path: str = ''
1185
+ self.max_state: int = 0
1186
+ self.work_path: str = work_path
1187
+ self.cs_path: str = os.path.join(os.path.join(self.work_path, 'cross-section'))
1188
+ # todo 3: check whether this folder exists, and exit
1189
+ if self.exist_path(self.cs_path):
1190
+ self.rm(self.cs_path)
1191
+ self.mkdir(self.cs_path)
1192
+ self.E_data_list: List[List[float]] = []
1193
+ self.f_data_list: List[List[float]] = []
1194
+ self.delta: float = 0.01
1195
+ self._coeff_calc(self.delta) # todo 2: can redefine the delta value in the inherited class
1196
+ self._output_name: str = ''
1197
+ self.x_min: float = 0.0
1198
+ self.x_max: float = 0.0
1199
+
1200
+ def _coeff_calc(self, delta: float):
1201
+ # gauss function const
1202
+ self.coeff: float = 1 / (delta * math.sqrt(self.pi / 2))
1203
+ self.devided_num: float = - delta**2 / 2
1204
+
1205
+ def _calc_cs(self, data_path: str, max_state: int, delta: float, output_name: str):
1206
+ self.data_path = data_path
1207
+ self.max_state = max_state
1208
+ self.delta = delta
1209
+ self._output_name = output_name
1210
+ self._coeff_calc(delta)
1211
+ self._read_all_data(data_path)
1212
+ self._calc_with_data()
1213
+
1214
+ def _calc_at_de(self, de: float) -> Tuple[float, float, float]:
1215
+ spect = 0.0
1216
+ whole_num_point = len(self.E_data_list[0])
1217
+ for state_index, E_data in enumerate(self.E_data_list):
1218
+ f_data = self.f_data_list[state_index]
1219
+ for target_index, E in enumerate(E_data):
1220
+ f = f_data[target_index]
1221
+ spect += E * f * self._gauss(de, E)
1222
+ cross_section = self.cs_coeff * spect * self.gamma / (whole_num_point * de)
1223
+ wavelength = self.ev2nm / de
1224
+ return de, wavelength, cross_section
1225
+
1226
+ def _calc_SPC(self, E_list: List[float], f_list: List[float], delta: float, output_name: str):
1227
+ self.delta = delta
1228
+ self._coeff_calc(delta)
1229
+ self.E_data_list = [[x] for x in E_list]
1230
+ self.f_data_list = [[x] for x in f_list]
1231
+ self._output_name = output_name
1232
+ self._calc_with_data()
1233
+
1234
+ def _calc_with_data(self):
1235
+ ##### data pre-process #####
1236
+ E_max = -10000
1237
+ E_min = 10000
1238
+ # whole_num_point = 0
1239
+ for E_data in self.E_data_list:
1240
+ tmp_max = max(E_data)
1241
+ tmp_min = min(E_data)
1242
+ E_max = tmp_max if tmp_max > E_max else E_max
1243
+ E_min = tmp_min if tmp_min < E_min else E_min
1244
+ # whole_num_point = len(self.E_data_list[0]) # todo: check the E and f is has the same number
1245
+ range_min = E_min - self.kappa * self.delta
1246
+ range_max = E_max + self.kappa * self.delta
1247
+ self.x_min = range_min
1248
+ self.x_max = range_max
1249
+ # de = round(range_min, 4)
1250
+ de: float = range_min
1251
+
1252
+ ##### calc cross section #####
1253
+ file_handle = open(os.path.join(self.cs_path, self._output_name), 'w')
1254
+ file_handle.write('DE/eV lambda/nm sigma/A^2\n')
1255
+ de_list = []
1256
+ while de < range_max:
1257
+ de_list.append(de)
1258
+ de += self.eps
1259
+
1260
+ whole_num_point = len(self.E_data_list[0])
1261
+ c_E_list = ((c_double * whole_num_point) * self.max_state)()
1262
+ c_f_list = ((c_double * whole_num_point) * self.max_state)()
1263
+ for state_index, E_data in enumerate(self.E_data_list):
1264
+ f_data = self.f_data_list[state_index]
1265
+ for target_index, E in enumerate(E_data):
1266
+ f = f_data[target_index]
1267
+ c_E_list[state_index][target_index] = E
1268
+ c_f_list[state_index][target_index] = f
1269
+ de_num = len(de_list)
1270
+ c_cs = (c_double * de_num)()
1271
+ c_de_list = (c_double* de_num)()
1272
+ wavelength = []
1273
+ for idx, de in enumerate(de_list):
1274
+ c_de_list[idx] = de
1275
+ wavelength.append(self.ev2nm / de)
1276
+ cs = CDLL(os.path.join(py_script_path, 'cs.so'))
1277
+ c_delta = c_double(self.delta)
1278
+ c_coeff = c_double(self.coeff)
1279
+ c_cs_coeff = c_double(self.cs_coeff)
1280
+ c_de_num = c_long(de_num)
1281
+ # print(f'coeff: {self.coeff}, divided: {self.devided_num}')
1282
+ cs.cs_calc(c_E_list, c_f_list,
1283
+ self.max_state, whole_num_point,
1284
+ c_delta, c_coeff, c_de_num, c_cs_coeff,
1285
+ c_cs, c_de_list)
1286
+ cross_section = []
1287
+ for cs_single in c_cs:
1288
+ cross_section.append(cs_single)
1289
+ for idx, de in enumerate(de_list):
1290
+ w, c = wavelength[idx], cross_section[idx]
1291
+ file_handle.write('%.4f %.4f %.8f\n' % (de, w, c))
1292
+
1293
+
1294
+ # pool = mp.get_context('spawn').Pool()
1295
+ # result_list = pool.map(self._calc_at_de, de_list)
1296
+ # pool.close()
1297
+ # pool.join()
1298
+ # for each in result_list:
1299
+ # de, wavelength, cross_section = each
1300
+ # file_handle.write('%.4f %.4f %.8f\n' % (de, wavelength, cross_section))
1301
+ file_handle.close()
1302
+
1303
+ def _gauss(self, E: float, dE_0n: float) -> float:
1304
+ # coeff = 1 / (c.delta * math.sqrt(pi / 2))
1305
+ # devided_num = - c.delta**2 / 2
1306
+ to_be_exp = ((E - dE_0n) ** 2) / self.devided_num
1307
+ return self.coeff * math.exp(to_be_exp)
1308
+
1309
+ def _read_all_data(self, data_path: str):
1310
+ for idx in range(1, self.max_state + 1):
1311
+ self.E_data_list.append(self._read_single_data(os.path.join(data_path, f'E{idx}.dat')))
1312
+ self.f_data_list.append(self._read_single_data(os.path.join(data_path, f'f{idx}.dat')))
1313
+
1314
+ def _read_single_data(self, file_path: str) -> List[float]:
1315
+ with open(file_path, 'r') as f:
1316
+ tmp = f.read().splitlines()
1317
+ data = list(map(float, tmp))
1318
+ return data
1319
+
1320
+
1321
+ class TD_cross_section(CrossSection_calc):
1322
+ def __init__(self, work_path: str) -> None:
1323
+ super().__init__(work_path)
1324
+ # self.TD_out_name = 'cross-section-TD.dat'
1325
+ # self.SPC_out_name = 'cross-section-SPC.dat'
1326
+ self.TD_out_name = 'cross-section_qc-nea.dat'
1327
+ self.SPC_out_name = 'cross-section_spc.dat'
1328
+
1329
+ @logger('start calc QC cross section')
1330
+ def calc(self, data_path: str, max_state: int, delta: float=0.05, output_name: str='cross-section-TD.dat'):
1331
+ super()._calc_cs(data_path, max_state, delta, output_name=output_name)
1332
+ # self.cp(os.path.join(self.cs_path, 'cross-section-TD.dat'), self.work_path)
1333
+
1334
+ def calc_SPC(self, E_list: List[float], f_list: List[float], delta: float, out_name: str):
1335
+ super()._calc_SPC(E_list, f_list, delta, out_name)
1336
+ # self.cp(os.path.join(self.cs_path, self.SPC_out_name), self.work_path)
1337
+
1338
+
1339
+ class ML_cross_section(CrossSection_calc, ML_calc):
1340
+ def __init__(self, work_path: str, program_path: str) -> None:
1341
+ # super().__init__(work_path)
1342
+ CrossSection_calc.__init__(self, work_path)
1343
+ ML_calc.__init__(self, program_path, work_path)
1344
+ self.ML_path = os.path.join(self.work_path, 'ML')
1345
+ self.new_dir(self.ML_path)
1346
+ # if not self.exist_path(self.ML_path):
1347
+ # self.mkdir(self.ML_path)
1348
+ self.crrent_path: str = ''
1349
+ self.data_path: str = ''
1350
+ self.max_state: int = 0
1351
+ self.TD_point: int = 0
1352
+ self.xyz_file: str = ''
1353
+ self.this: CtlPartAdapter = None
1354
+ self.use_model_pred_path: str = os.path.join(self.ML_path, 'useModel')
1355
+ self.new_dir(self.use_model_pred_path)
1356
+ self.pred_data_path: str = os.path.join(self.ML_path, 'pred_data')
1357
+ self.new_dir(self.pred_data_path)
1358
+ self.mix_data_path: str = os.path.join(self.ML_path, 'mix_data')
1359
+ self.new_dir(self.mix_data_path)
1360
+
1361
+ self.train_name: str = 'itrain.dat'
1362
+ self.sub_name: str = 'isubtrain.dat'
1363
+ self.val_name: str = 'ivalidate.dat'
1364
+ self.eq_name: str = 'eq.xyz'
1365
+ self.xyz_name: str = 'xyz.dat'
1366
+ self.ML_out_name: str = 'cross-section_ml-nea.dat' # self.output_file_name: str = 'cross-section-ML.dat'
1367
+
1368
+ def define_data_set(self, this: 'CtlPartAdapter', TD_point: int, xyz_file: str, max_state: int, delta: float=0.01):
1369
+ self.TD_point = TD_point
1370
+ self.xyz_file = xyz_file
1371
+ self.max_state = max_state
1372
+ self.delta = delta
1373
+ self.this = this
1374
+ # to separate each
1375
+
1376
+ # @logger('iteratively calc TD with gaussian')
1377
+ def _iter_calc_QC(self, start: int, end: int):
1378
+ calc, start, end = self.this.recheck_QC_calc(start, end)
1379
+ if calc:
1380
+ for i in range(start, end):
1381
+ if not EnvCheck.check_gaussian():
1382
+ exit()
1383
+ if self.this.gauss_calc_inp_exist:
1384
+ self.this.gauss.gaussian_TD_calc_with_template(i, self.this.multi_xyz[i])
1385
+ else:
1386
+ self.this.gauss.gaussain_TD_with_para(idx=i, molecule=self.this.multi_xyz[i], functionals=self.this.functionals,
1387
+ basis=self.this.basis, nStates=self.this.N_max_state)
1388
+ self.this.gauss.process_gaussian_log(self.this.gauss.all_log_path, self.this.N_max_state, pattern='', regex=False)
1389
+ else:
1390
+ self.this.gauss.process_top_n_E_f(self.this.gauss.all_log_path, self.this.N_max_state, end)
1391
+
1392
+ @logger('use all QC points to run ML-NEA calculations')
1393
+ def ML_train_all(self):
1394
+ # todo 3: to to record log
1395
+ self._iter_calc_QC(0, self.TD_point)
1396
+ rmse = self._train_epoch(self.TD_point)
1397
+ print(f'\nRMSE_geom value for {self.TD_point} point: {rmse}')
1398
+ self._train_final(self.TD_point)
1399
+ self._collect_ML_result(self.use_model_pred_path)
1400
+ self.mix_multiple_file(self.max_state, self.pred_data_path, self.this.gauss.gauss_data_path, self.mix_data_path)
1401
+ self._calc_cs(self.mix_data_path, self.max_state, self.delta, self.ML_out_name)
1402
+ # self.cp(os.path.join(self.cs_path, 'cross-section-ML.dat'), self.work_path)
1403
+
1404
+ @logger('run ML-NEA iteratively for spectrum generation')
1405
+ def ML_train_iter(self, start_point: int=50, incr_point: int=50, threshold: float=0.05):
1406
+ self.cd(self.ML_path)
1407
+ current_point = start_point
1408
+ self._iter_calc_QC(0, current_point) # the 0th is the equilibrium geom, calc ( 0 - 49 ) all 50 point
1409
+ prev_rmse = 0.0
1410
+ iter_cnt = 1
1411
+ # attention: In fact, we have TD_point number: (self.TD_point + 1)
1412
+ while current_point < self.TD_point + 1:
1413
+ rmse = self._train_epoch(current_point)
1414
+ rRMSE = abs(rmse - prev_rmse) / rmse
1415
+ print(f'ML-NEA iteration {iter_cnt}: train_number = {current_point}; RMSE_geom = {rmse}; rRMSE = {rRMSE}\n')
1416
+ iter_cnt += 1
1417
+ # todo 4: print log about the RMSE variation
1418
+ if rRMSE <= threshold:
1419
+ # calc cross section
1420
+ print(f'ML-NEA iteration ended after {iter_cnt - 1} iteration!')
1421
+ # todo 5: extract all data and mix data
1422
+ self._train_final(current_point)
1423
+ self._collect_ML_result(self.use_model_pred_path)
1424
+ self.mix_multiple_file(self.max_state, self.pred_data_path, self.this.gauss.gauss_data_path, self.mix_data_path)
1425
+ self._calc_cs(self.mix_data_path, self.max_state, self.delta, self.ML_out_name)
1426
+ break
1427
+ else:
1428
+ if current_point + incr_point >= self.TD_point + 1:
1429
+ self._iter_calc_QC(current_point, self.TD_point + 1)
1430
+ current_point = self.TD_point + 1
1431
+ print(f'max training number exceeded, use all point for ML-NEA training\n')
1432
+ rmse = self._train_epoch(current_point)
1433
+ print(f'ML-NEA iteration {iter_cnt}: train_number = {current_point}; RMSE_geom = {rmse}; rRMSE = {rRMSE}\n')
1434
+ self._train_final(current_point)
1435
+ self._collect_ML_result(self.use_model_pred_path)
1436
+ self.mix_multiple_file(self.max_state, self.pred_data_path, self.this.gauss.gauss_data_path, self.mix_data_path)
1437
+ self._calc_cs(self.mix_data_path, self.max_state, self.delta, self.ML_out_name)
1438
+ else:
1439
+ self._iter_calc_QC(current_point, current_point + incr_point)
1440
+ current_point += incr_point
1441
+ prev_rmse = rmse
1442
+ # self.cp(os.path.join(self.cs_path, 'cross-section-ML.dat'), self.work_path)
1443
+
1444
+ def _train_epoch(self, train_num: int) -> float:
1445
+ self.cd(self.ML_path)
1446
+ train_num_path = os.path.join(self.ML_path, str(train_num))
1447
+ if self.exist_path(train_num_path):
1448
+ self.rm(train_num_path)
1449
+ self.mkdir(train_num_path); self.cd(train_num_path)
1450
+ Molecule.multi_print_xyz(self.this.multi_xyz, self.xyz_name, train_num)
1451
+ _, sub, val = self._create_idx_file(train_num)
1452
+ rmse_list = []
1453
+ for task_sym in ['E', 'f']:
1454
+ task_path = os.path.join(train_num_path, task_sym)
1455
+ self.new_dir(task_path)
1456
+ for state in range(1, self.max_state + 1):
1457
+ sym_state = task_sym + str(state)
1458
+ self.cd(task_path); current_dir = os.path.join(task_path, str(state)); self.mkdir(current_dir); self.cd(current_dir)
1459
+ self.ln(os.path.join(train_num_path, self.xyz_name), self.xyz_name)
1460
+ self.ln(os.path.join(self.this.gauss.gauss_data_path, f'{sym_state}.dat'), f'{sym_state}')
1461
+ self.ln(os.path.join(train_num_path, self.train_name), self.train_name)
1462
+ self.ln(os.path.join(train_num_path, self.sub_name), self.sub_name)
1463
+ self.ln(os.path.join(train_num_path, self.val_name), self.val_name)
1464
+ self.ln(os.path.join(self.ML_path, self.eq_name), self.eq_name)
1465
+
1466
+ self.set_task_type(createMLmodel=True)
1467
+ self.set_data_set_inp_file(XYZfile='xyz.dat', Yfile=f'{sym_state}')
1468
+ self.set_est_out(YestFile=f'{sym_state}.est')
1469
+ self.set_kernel(Gaussian=True)
1470
+ self.set_model_out(MLmodelOut=f'{sym_state}.unf')
1471
+ self.add_other('molDescriptor', 'RE'); self.add_other('molDescrType', 'unsorted')
1472
+ self.set_hyper_parameter(Lambda='opt', NlgLambda=11, lgLambdaL=-25, lgLambdaH=-1, Sigma='opt', lgSigmaL=-10, lgSigmaH=9, NlgSigma=11, lgOptDepth=2)
1473
+ self.add_other('minimizeError', 'RMSE')
1474
+ self.set_sampling(user_defined=True)
1475
+ self.set_index_num(Ntrain=train_num, NSubtrain=sub, Nvalidate=val)
1476
+ self.set_index_file(iTrainin=self.train_name, iSubtrainin=self.sub_name, iValidatein=self.val_name)
1477
+ self.run_ML(current_dir)
1478
+ rmse_value = self.collect_validate_RMSE()
1479
+ if rmse_value != 0.0:
1480
+ rmse_list.append(rmse_value)
1481
+ return (reduce(lambda x, y: x * y, rmse_list)) ** (1 / len(rmse_list))
1482
+
1483
+ def _train_final(self, train_num: int):
1484
+ self.cd(self.use_model_pred_path)
1485
+ Molecule.multi_print_xyz(self.this.multi_xyz, os.path.join(self.ML_path, self.xyz_name))
1486
+ for sym, state in product(['E', 'f'], range(1, self.max_state + 1)):
1487
+ self.cd(self.use_model_pred_path)
1488
+ sym_state = sym + str(state)
1489
+ self.mkdir(sym); self.cd(sym); self.mkdir(str(state)); self.cd(str(state))
1490
+ self.ln(os.path.join(self.ML_path, self.xyz_name), self.xyz_name)
1491
+ self.ln(os.path.join(self.ML_path, self.eq_name), self.eq_name)
1492
+ self.ln(os.path.join(self.ML_path, str(train_num), sym, str(state), f'{sym_state}.unf'), f'{sym_state}.unf')
1493
+
1494
+ self.set_task_type(useMLmodel=True)
1495
+ self.set_data_set_inp_file(XYZfile=self.xyz_name)
1496
+ self.set_est_out(YestFile=f'{sym_state}.est')
1497
+ self.set_kernel(Gaussian=True)
1498
+ self.set_model_inp(MLmodelIn=f'{sym_state}.unf')
1499
+ self.add_other('molDescriptor', 'RE'); self.add_other('molDescrType', 'unsorted')
1500
+ self.run_ML(os.getcwd())
1501
+
1502
+ def _collect_ML_result(self, final_path: str):
1503
+ for sym, state in product(['E', 'f'], range(1, self.max_state + 1)):
1504
+ sym_state = sym + str(state)
1505
+ self.ln(os.path.join(final_path, sym, str(state), f'{sym_state}.est'), os.path.join(self.pred_data_path, f'{sym_state}.dat'))
1506
+
1507
+ def _create_idx_file(self, train_num: int, train_ratio: float=0.8) -> Tuple[int, int, int]:
1508
+ sub = int(train_num * train_ratio)
1509
+ val = train_num - sub
1510
+ self._write_seq(train_num, self.train_name)
1511
+ self._write_seq(sub, self.sub_name)
1512
+ self._write_seq(start=sub + 1, end=train_num, name=self.val_name)
1513
+ return train_num, sub, val
1514
+
1515
+ def _write_seq(self, end: int, name: str, start: int=1):
1516
+ with open(name, 'w') as f:
1517
+ for i in range(start, end + 1):
1518
+ f.write(str(i) + '\n')
1519
+
1520
+
1521
+ class SPC_cross_section():
1522
+ h_evs = 4.13566733E-15 # h eV.s
1523
+ light = 299792458 # speed of light m/s
1524
+ ev2nm = 1E9 * h_evs * light
1525
+ SPC_out_name = 'cross-section_spc.dat'
1526
+ def __init__(self) -> None:
1527
+ self.Es: List[float] = []
1528
+ self.fs: List[float] = []
1529
+ self.x_min: float = 0.0
1530
+ self.x_max: float = 0.0
1531
+ self.delta: float = 0.0
1532
+
1533
+ def read_data_from_file(self, file_path: str) -> bool:
1534
+ try:
1535
+ with open(file_path) as f:
1536
+ data = f.read()
1537
+ for line in data:
1538
+ delta_E, f_osci = line.split()
1539
+ delta_E = float(delta_E)
1540
+ f_osci = float(f_osci)
1541
+ self.Es.append(delta_E)
1542
+ self.fs.append(f_osci)
1543
+ except:
1544
+ return False
1545
+ return True
1546
+
1547
+ def read_data_from_para(self, E_list: List[float], f_list: List[float]):
1548
+ self.Es = E_list
1549
+ self.fs = f_list
1550
+
1551
+ def set_para(self, delta: float, x_min: float, x_max: float):
1552
+ self.x_min = x_min
1553
+ self.x_max = x_max
1554
+ self.delta = delta
1555
+
1556
+ @staticmethod
1557
+ def arange(start: float, end: float, step: float) -> Iterable[float]:
1558
+ if end <= start:
1559
+ yield start
1560
+ else:
1561
+ current = start
1562
+ while current <= end:
1563
+ yield current
1564
+ current += step
1565
+
1566
+ @staticmethod
1567
+ def SPCfunc(delta: float, peak_position: float, peak_height: float, x: float):
1568
+ # delta: broadening parameter; ff: oscillator strength; EE: current inputed E to calculate SPC
1569
+ nn = 1.0
1570
+ E_deviation = 0.0
1571
+ result = ( 0.619 * nn * peak_height / delta
1572
+ * math.exp( -(x - peak_position + E_deviation) ** 2 / (delta ** 2) ) )
1573
+ return result
1574
+
1575
+ def calc(self, out_name: str):
1576
+ step = 0.002
1577
+ try:
1578
+ with open(out_name, 'w') as f:
1579
+ f.write('DE/eV lambda/nm sigma/A^2\n')
1580
+ for x in self.arange(self.x_min, self.x_max, step):
1581
+ sigma: float = 0.0
1582
+ for idx, peak_position in enumerate(self.Es):
1583
+ peak_height = self.fs[idx]
1584
+ sigma += self.SPCfunc(self.delta, peak_position, peak_height, x)
1585
+ wavelength = self.ev2nm / x
1586
+ # f.write(f'{x}\t{wavelength}\t{sigma}\n')
1587
+ f.write('%.4f %.4f %.8f\n' % (x, wavelength, sigma))
1588
+ except:
1589
+ err_print('SPC-cross section calculate fail')
1590
+
1591
+
1592
+ class EnvCheck(LinuxInterface):
1593
+ @classmethod
1594
+ def check_NX(cls) -> bool:
1595
+ _, output = cls.exec('echo $NX')
1596
+ if output.strip() != '':
1597
+ return True
1598
+ cls.print_warning('$NX variable not set!!!!!')
1599
+ return False
1600
+
1601
+ @classmethod
1602
+ def check_mlatom(cls) -> bool:
1603
+ if cls.exist_path('MLatom.py'):
1604
+ return True
1605
+ cls.print_warning('ML-NEA.py is not at the same directory with MLatom.py!!!!!')
1606
+ return False
1607
+
1608
+ @classmethod
1609
+ def check_gaussian(cls) -> bool:
1610
+ _, output = cls.exec(f'export | grep GAUSS_EXEDIR')
1611
+ if output.strip() != '':
1612
+ return True
1613
+ cls.print_warning('Gaissian software not exists or not be source, $GAUSS_EXEDIR variable not exists!!!!!')
1614
+ return False
1615
+
1616
+ @classmethod
1617
+ def check_gaussian_version(cls) -> str:
1618
+ # attention: use which output may be empty or " /usr/bin/which: no *** in "
1619
+ _, output = cls.exec('export | grep GAUSS_EXEDIR | grep g16')
1620
+ if output.strip() != '':
1621
+ return 'g16'
1622
+ _, output = cls.exec('export | grep GAUSS_EXEDIR | grep g09')
1623
+ if output.strip() != '':
1624
+ return 'g09'
1625
+ # cls.print_warning('Gaussian 09 or Gaussian 16 not exists!!!!! If you use old version of Gaussian ,please update it!!!!!')
1626
+ # exit()
1627
+ return ''
1628
+ # todo 3: raise an error that no Gaussian exists
1629
+
1630
+ @classmethod
1631
+ def check_python3(cls) -> bool:
1632
+ _, out = cls.exec('echo $python3')
1633
+ if out.strip() != '':
1634
+ return True
1635
+ # print('$python3 variable not found, be attention, it may run into error at some situation!!!')
1636
+ return False
1637
+
1638
+ @staticmethod
1639
+ def check_matplotlib() -> bool:
1640
+ if dir('plt'):
1641
+ return True
1642
+ return False
1643
+
1644
+
1645
+ class Dialog(LinuxInterface):
1646
+ def __init__(self):
1647
+ self.forbidden_list: List[str] = []
1648
+ pass
1649
+
1650
+ def _judge_inp(self, inp: str) -> Tuple[type, Any]:
1651
+ if inp.strip().lower() == 'true':
1652
+ return bool, True
1653
+ elif inp.strip().lower() == 'false':
1654
+ return bool, False
1655
+ else:
1656
+ try:
1657
+ tmp = int(inp)
1658
+ except:
1659
+ try:
1660
+ tmp = float(inp)
1661
+ except:
1662
+ return str, inp
1663
+ else:
1664
+ return float, tmp
1665
+ else:
1666
+ return int, tmp
1667
+
1668
+ def set_forbidden(self, forbidden_list: List[str]):
1669
+ self.forbidden_listj = forbidden_list
1670
+
1671
+ class Decorator():
1672
+ @staticmethod
1673
+ def defer_print(content: str) -> Callable[[Any], Any]:
1674
+ def defer_decorator(func: Callable[[Any], Any]) -> Callable[[Any], Any]:
1675
+ # @wraps(func)
1676
+ def defer_wrap(self: object, *args: Any, **kwargs: Any) -> Any:
1677
+ result = func(self, *args, **kwargs)
1678
+ print(content)
1679
+ return result
1680
+ return defer_wrap
1681
+ return defer_decorator
1682
+
1683
+ @Decorator.defer_print('-' * 80)
1684
+ def ask(self, idx: float, prompt: str,
1685
+ description: str, default: str, require_type: type,
1686
+ upper_lmt: _Int_or_Float=0, lower_lmt: _Int_or_Float=0) -> Any:
1687
+ # inp_value = input(f'step {idx}: {prompt}' + \
1688
+ # f'\n {description}' + \
1689
+ # f'\n default input: {default}' + \
1690
+ # f'\n your value: ')
1691
+ print('')
1692
+ inp_value = input(f'{prompt}' + \
1693
+ f'\n\t{description}' + \
1694
+ f'\ndefault input: {default}\tyour value: ').strip()
1695
+ while True:
1696
+ if inp_value == '':
1697
+ if require_type == bool and default.strip().lower() == 'true':
1698
+ return True
1699
+ elif require_type == bool and default.strip().lower() == 'false':
1700
+ return False
1701
+ else:
1702
+ return require_type(default)
1703
+ if inp_value in self.forbidden_list:
1704
+ inp_value = input('this input contradicts with some internal variable, please re-input: ').strip()
1705
+ continue
1706
+ inp_type, trans_value = self._judge_inp(inp_value)
1707
+ if inp_type != require_type:
1708
+ inp_value = input('it requires a ' + repr(require_type).split("\'")[1] + ' type, please re-input: ').strip()
1709
+ continue
1710
+ if require_type == int or require_type == float:
1711
+ if trans_value > upper_lmt or trans_value < lower_lmt:
1712
+ inp_value = input(f'the value must meet the requirement: {lower_lmt} <= x <= {upper_lmt} , please re-input: ').strip()
1713
+ continue
1714
+ break
1715
+ return trans_value
1716
+
1717
+
1718
+ class Config(Dialog):
1719
+ def __init__(self):
1720
+ super(Config, self).__init__()
1721
+ self.config_name: str = 'config'
1722
+ self.start_point: int = 0
1723
+ self.has_config: bool = False
1724
+ ##### xyz file path #####
1725
+ self.xyz_file: str = 'geom.xyz'
1726
+ self.multi_xyz_file: str = 'xyz.dat'
1727
+ self.multi_xyz_usage: int = -1
1728
+ ##### gauss input file path #####
1729
+ self.gauss_opt_inp_file: str = 'opt.com'
1730
+ self.gauss_opt_inp_exist: bool = False
1731
+ self.gauss_calc_inp_file: str = 'calc.com'
1732
+ self.gauss_calc_inp_exist: bool = False
1733
+ ##### gauss inp detail #####
1734
+ self.cpu_cores: int = 0
1735
+ self.memory_limit: int = 0
1736
+ self.functionals: str = ''
1737
+ self.basis: str = ''
1738
+ self.charge: int = 0
1739
+ self.multiplicity: int = 0
1740
+ ##### ML-NEA defination part #####
1741
+ self.N_TD_NEA_point: int = 0
1742
+ self.N_ML_NEA_point: int = 0
1743
+ self.N_max_state: int = 0
1744
+ self.use_all_point: bool = False
1745
+ ##### ML-NEA procedure part #####
1746
+ self.train_start_point: int = 0
1747
+ self.train_incr_point: int = 0
1748
+ self.rRMSE_threshould: float = 0.0
1749
+ ##### cross section parameter #####
1750
+ self.TD_delta: float = 0.0
1751
+ self.ML_delta: float = 0.0
1752
+
1753
+ if self.exist_path(self.config_name):
1754
+ self.read_config()
1755
+ self.has_config = True
1756
+ else:
1757
+ self.ask_config()
1758
+ self.write_config()
1759
+ print('#' * 80 + f'\nconfiguration has been written as {self.config_name}, please re-run this program to calculate!!!\n' + '#' * 80)
1760
+
1761
+ def read_config(self):
1762
+ with open(self.config_name) as f:
1763
+ config_dict = json.loads(f.read())
1764
+ self.start_point = config_dict['start_point']
1765
+ self.has_config = config_dict['has_config']
1766
+ self.xyz_file = config_dict['xyz_file']
1767
+ self.multi_xyz_file = config_dict['multi_xyz_file']
1768
+ self.multi_xyz_usage = config_dict['multi_xyz_usage']
1769
+ self.gauss_opt_inp_file = config_dict['gauss_opt_inp_file']
1770
+ self.gauss_opt_inp_exist = config_dict['gauss_opt_inp_exist']
1771
+ self.gauss_calc_inp_file = config_dict['gauss_calc_inp_file']
1772
+ self.gauss_calc_inp_exist = config_dict['gauss_calc_inp_exist']
1773
+ self.cpu_cores = config_dict['cpu_cores']
1774
+ self.memory_limit = config_dict['memory_limit']
1775
+ self.functionals = config_dict['functionals']
1776
+ self.basis = config_dict['basis']
1777
+ self.charge = config_dict['charge']
1778
+ self.multiplicity = config_dict['multiplicity']
1779
+ self.N_TD_NEA_point = config_dict['N_TD_NEA_point']
1780
+ self.N_ML_NEA_point = config_dict['N_ML_NEA_point']
1781
+ self.N_max_state = config_dict['N_max_state']
1782
+ self.use_all_point = config_dict['use_all_point']
1783
+ self.train_start_point = config_dict['train_start_point']
1784
+ self.train_incr_point = config_dict['train_incr_point']
1785
+ self.rRMSE_threshould = config_dict['rRMSE_threshould']
1786
+ self.TD_delta = config_dict['TD_delta']
1787
+ self.ML_delta = config_dict['ML_delta']
1788
+
1789
+ def write_config(self):
1790
+ config_dict = {
1791
+ 'start_point' : self.start_point,
1792
+ 'has_config' : self.has_config,
1793
+ 'xyz_file' : self.xyz_file,
1794
+ 'multi_xyz_file' : self.multi_xyz_file,
1795
+ 'multi_xyz_usage' : self.multi_xyz_usage,
1796
+ 'gauss_opt_inp_file' : self.gauss_opt_inp_file,
1797
+ 'gauss_opt_inp_exist' : self.gauss_opt_inp_exist,
1798
+ 'gauss_calc_inp_file' : self.gauss_calc_inp_file,
1799
+ 'gauss_calc_inp_exist' : self.gauss_calc_inp_exist,
1800
+ 'cpu_cores' : self.cpu_cores,
1801
+ 'memory_limit' : self.memory_limit,
1802
+ 'functionals' : self.functionals,
1803
+ 'basis' : self.basis,
1804
+ 'charge' : self.charge,
1805
+ 'multiplicity' : self.multiplicity,
1806
+ 'N_TD_NEA_point' : self.N_TD_NEA_point,
1807
+ 'N_ML_NEA_point' : self.N_ML_NEA_point,
1808
+ 'N_max_state' : self.N_max_state,
1809
+ 'use_all_point' : self.use_all_point,
1810
+ 'train_start_point' : self.train_start_point,
1811
+ 'train_incr_point' : self.train_incr_point,
1812
+ 'rRMSE_threshould' : self.rRMSE_threshould,
1813
+ 'TD_delta' : self.TD_delta,
1814
+ 'ML_delta' : self.ML_delta,
1815
+ }
1816
+ with open(self.config_name, 'w') as f:
1817
+ f.write(json.dumps(config_dict))
1818
+
1819
+ def ask_config(self):
1820
+ self._judge_task_type()
1821
+ if self.start_point == 0:
1822
+ self.ask_task_type_0()
1823
+ elif self.start_point == 1:
1824
+ # self.ask_task_type_1()
1825
+ pass
1826
+ elif self.start_point == 2:
1827
+ pass
1828
+ elif self.start_point == 3:
1829
+ pass
1830
+ else:
1831
+ pass
1832
+
1833
+ def ask_task_type_0(self):
1834
+ self._ask_max_state()
1835
+ self._ask_TD_NEA_point()
1836
+ self._ask_ML_NEA_point()
1837
+ self._ask_use_all_point()
1838
+ if self.exist_path(self.gauss_opt_inp_file) and GaussCalc.check_opt_freq_inp(self.gauss_opt_inp_file):
1839
+ # todo 5: gaussian inp file check integrition : to Gauss_calc part check
1840
+ self.gauss_opt_inp_exist = True
1841
+ else:
1842
+ if not self.exist_path(self.xyz_file):
1843
+ self._ask_xyz_file()
1844
+ if self.exist_path(self.gauss_calc_inp_file) and GaussCalc.check_TD_inp(self.gauss_calc_inp_file):
1845
+ # todo 5: gaussian inp file check integrition : to Gauss_calc part check
1846
+ self.gauss_calc_inp_exist = True
1847
+ if (not self.gauss_opt_inp_exist) or (not self.gauss_calc_inp_exist):
1848
+ self._ask_gauss_inp_detail()
1849
+ self._ask_ML_iteration_info()
1850
+ self._ask_delta_value()
1851
+ # NX part
1852
+
1853
+ def ask_task_type_1(self):
1854
+ self._ask_max_state()
1855
+ self._ask_TD_NEA_point()
1856
+ self._ask_use_all_point() # remember to set the ML point number as the inputed xyz.dat data number
1857
+ if not self.exist_path(self.multi_xyz_file):
1858
+ self._ask_multi_xyz_file()
1859
+ if not self.exist_path(self.gauss_calc_inp_file):
1860
+ self._ask_gauss_inp_detail()
1861
+ else:
1862
+ self.gauss_calc_inp_exist = True
1863
+ # todo 5: remember to check whether the gauss inp file is correct
1864
+ self._ask_ML_iteration_info()
1865
+ self._ask_delta_value()
1866
+ # self._ask_multi_xyz_used_for() ##### only use all point for ML prediction, and use very little for QC calc
1867
+ # if self.multi_xyz_usage == 0: # all point used for ML prediction
1868
+ # self._ask_TD_NEA_point()
1869
+ # # todo 4: to check whether ML_point > TD_point
1870
+ # elif self.multi_xyz_usage == 1: # all point used for TD calc reference
1871
+ # self._ask_ML_NEA_point()
1872
+ # # todo 5: need to run NX-MD for additional point
1873
+ # # todo 4: to check whether ML_point > TD_point
1874
+
1875
+ def ask_task_type_2(self):
1876
+ pass
1877
+
1878
+ def ask_task_type_3(self):
1879
+ pass
1880
+
1881
+ def _judge_task_type(self):
1882
+ self.start_point = self.ask(idx=0.0, prompt='select where you want to start with',
1883
+ description='0. I only have one xyz file (not need to be eqilibrium one)\n' +
1884
+ '\t1. I have multiple xyz file (you should ensure the point is enough)\n' +
1885
+ '\t2. I have multiple gaussian calculation output file (file name should be ends with .log)\n' +
1886
+ '\t3. I have E and f data of many excitation state calculated from multiple xyz geometry file',
1887
+ default='0', require_type=int, upper_lmt=3, lower_lmt=0)
1888
+
1889
+ def _ask_xyz_file(self):
1890
+ self.xyz_file = self.ask(idx=0.1, prompt='define the xyz file name',
1891
+ description='Please input the path/file_name of the xyz file(no need to be optimized geometry)',
1892
+ default='geom.xyz', require_type=str)
1893
+
1894
+ def _ask_au_unit(self):
1895
+ self.au_unit = self.ask(idx=0.2, prompt='define whether your xyz file is a.u. unit',
1896
+ description='Please input False(xyz file is angstrom unit) or True(xyz file is a.u. unit)',
1897
+ default='False', require_type=bool)
1898
+
1899
+ def _ask_TD_NEA_point(self):
1900
+ self.N_TD_NEA_point = self.ask(idx=0.5, prompt='define the max number of QC-NEA point',
1901
+ description='Please input the max number of molecules you want to calculate in order to get a QC cross-section spectra, ' + \
1902
+ '\n\tthis program will not perform all the calculation unless ML training is not converged at the max QC-NEA point traiing.',
1903
+ default='10', require_type=int, upper_lmt=999999999, lower_lmt=1) # orig_vale: 300
1904
+
1905
+ def _ask_max_state(self):
1906
+ self.N_max_state = self.ask(idx=0.0, prompt='define the number of excited states',
1907
+ description='Please input how many excited state you want to calculate with gaussian',
1908
+ default='3', require_type=int, upper_lmt=10000, lower_lmt=1) # orig_value: 10
1909
+
1910
+ def _ask_ML_NEA_point(self):
1911
+ self.N_ML_NEA_point = self.ask(idx=0.0, prompt='define the number of ML-NEA point',
1912
+ description='Please input how many point you want to predict and generate cross-section spectra with ML method',
1913
+ default='50', require_type=int, upper_lmt=999999999, lower_lmt=1) # orig_value: 300
1914
+
1915
+ def _ask_gauss_inp_detail(self):
1916
+ self.cpu_cores = self.ask(idx=0.0, prompt='define the cpu cores', require_type=int,
1917
+ description='Please input how many cores you want to calculate using Gaussian software',
1918
+ upper_lmt=9999999, lower_lmt=1, default='4')
1919
+ self.memory_limit = self.ask(idx=0.0, prompt='define the memory usage',
1920
+ description='Please input the memory (unit: GB) you want to calculate using Gaussian software',
1921
+ upper_lmt=9999999, lower_lmt=1, require_type=int, default='4')
1922
+ self.functionals = self.ask(idx=0.0, prompt='define the functionls of Gaussian input file', require_type=str,
1923
+ description='Please input the functionals', default='CAM-B3LYP')
1924
+ self.basis = self.ask(idx=0.0, prompt='define the basis of Gaussain input file', require_type=str,
1925
+ description='Please input the basis of Gaussian input file', default='def2TZVP')
1926
+ self.multiplicity = self.ask(idx=0.0, prompt='define the multiplicity of your molecule',
1927
+ description='Please input the multiplicity of your molecule.',
1928
+ require_type=int, default='1', upper_lmt=99999, lower_lmt=1)
1929
+ self.charge = self.ask(idx=0.0, prompt='define the charge of your molecule',
1930
+ description='Please input the charge of your molecule.',
1931
+ require_type=int, default='0', upper_lmt=99999, lower_lmt=-9999)
1932
+
1933
+ def _ask_use_all_point(self):
1934
+ self.use_all_point = self.ask(idx=0.0, prompt='define whether to use all point to train ML directly', default='False', require_type=bool,
1935
+ description='Please input True(it will use all point for ML training, no iteration) \n\tor False(will run on-the-fly to determin optima QC point to calc)')
1936
+
1937
+ def _ask_ML_iteration_info(self):
1938
+ self.train_start_point = self.ask(idx=0.0, prompt='define the ML starting training num', default='5', require_type=int, # default: 50
1939
+ description='Please input how many point you want to start with training', upper_lmt=99999999999999, lower_lmt=1)
1940
+ self.train_incr_point = self.ask(idx=0.0, prompt='define the increasement of ML training', default='2', require_type=int, # default: 50
1941
+ description='Please input how many point you want to increase in each ML iteration', upper_lmt=999999999, lower_lmt=1)
1942
+ self.rRMSE_threshould = self.ask(idx=0.0, prompt='define the threshould for rRMSE', default='0.1', require_type=float,
1943
+ description='Please input the threshould of rRMSE, which is the convergence criteria', upper_lmt=1.0, lower_lmt=1e-10)
1944
+
1945
+ def _ask_delta_value(self):
1946
+ self.TD_delta = self.ask(idx=0.0, prompt='define the delta value (broadening parameter) of TD-NEA', require_type=float,
1947
+ description='Please input the delta value for TD-NEA, unit: eV', default='0.05', upper_lmt=1.0, lower_lmt=1e-10)
1948
+ self.ML_delta = self.ask(idx=0.0, prompt='define the delta value (broadening parameter) of ML-NEA', require_type=float,
1949
+ description='Please input the delta value for ML-NEA, unit: eV', default='0.02', upper_lmt=1.0, lower_lmt=1e-10)
1950
+
1951
+ def _ask_multi_xyz_file(self): # entrance point 1 & 3: multiple xyz
1952
+ self.multi_xyz_file = self.ask(idx=1.0, prompt='define the file name/path of the multiple xyz file',
1953
+ description='Please input the name/path of the multiple xyz file, this file should be the consistance of pure xyz file' + \
1954
+ '\n\tbe conscious, the next xyz file and current xyz file should only be one line feed.',
1955
+ default='xyz.dat', require_type=str)
1956
+
1957
+ def _ask_multi_xyz_used_for(self): # entrance point 1: multiple xyz
1958
+ self.multi_xyz_usage = self.ask(idx=0.0, prompt='define the multiple xyz geometry is used for',
1959
+ description='Please input 0(used for ML prediction), or 1(used for QC calc reference)',
1960
+ default='0', require_type=int, upper_lmt=1, lower_lmt=0)
1961
+
1962
+ def _ask_gauss_folder(self): # entrance point 2: multiple gaussian output log
1963
+ self.gauss_folder_path = self.ask(idx=2.0, prompt='define the folder of multiple gauss output file',
1964
+ description='Please input the folder path where stores the multiple gaussian output file',
1965
+ default='gauss_log', require_type=str)
1966
+ self.gauss_log_pattern_type = self.ask(idx=2.1, prompt='define the pattern type of gaussian log file, you can choose globing ' + \
1967
+ 'or regular expression.', default='0', require_type=str, upper_lmt=1, lower_lmt=0,
1968
+ description='you can input 0(globing pattern) or 1(regular expression), if you don\'t know about it, press Enter!')
1969
+
1970
+ def _ask_globing_pattern(self): # entrance point 2: multiple gaussian output log
1971
+ self.gauss_log_pattern = self.ask(idx=2.5, prompt='define the globing pattern of gaussian log file', default='*.log',
1972
+ require_type=str, description='please input the globing pattern, "*" stands for any string, ' + \
1973
+ '"?" stands for any one character, you can also input nothing, \n\t' + \
1974
+ 'In default, it will search for all the file like 1.log 2.log 3.log ...')
1975
+
1976
+ def _ask_regex_pattern(self): # entrance point 2: multiple gaussian output log
1977
+ self.auss_log_pattern = self.ask(idx=2.5, prompt='define the regular expression of gaussian log file', default=r'[0-9]+?\.log',
1978
+ require_type=str, description='please input the regular expression pattern, ' + \
1979
+ ' "*" stands for any string, "?" stands for any one character')
1980
+
1981
+ def _ask_E_f_path(self): # entrance point 3: ask E and f file
1982
+ self.E_fdata_path = self.ask(idx=3.3, prompt='please input the folder of E and f file', default='data', require_type=str,
1983
+ description='Please ensure the file name is E1.dat ... E9.dat and f1.dat ... f9.dat')
1984
+
1985
+
1986
+ class ProcedureCtl(Config):
1987
+ def __init__(self):
1988
+ super(ProcedureCtl, self).__init__()
1989
+ if self.has_config:
1990
+ # todo 5: Environment check
1991
+ self.run_init()
1992
+ self.launch_task_according_flag()
1993
+
1994
+ def run_init(self): # todo 4: to determine whether the initialize should be put at here
1995
+ self.init_molecule: Molecule = Molecule()
1996
+ self.opt_molecule: Molecule = Molecule()
1997
+ self.py_script_path = os.path.abspath(__file__[:__file__.rfind('/')])
1998
+ self.work_path = os.getcwd()
1999
+ self.gauss = GaussCalc(self.py_script_path, self.work_path)
2000
+ self.nx = NewtonXCalc(self.work_path)
2001
+ self.cs_TD = TD_cross_section(self.work_path)
2002
+ self.cs_ML = ML_cross_section(self.work_path, self.py_script_path)
2003
+ self.multi_xyz: List[Molecule] = []
2004
+
2005
+ def launch_task_according_flag(self): # todo 4: to determine whether to add non-ML part
2006
+ if self.start_point == 0:
2007
+ if EnvCheck.check_gaussian() and EnvCheck.check_NX() and EnvCheck.check_mlatom():
2008
+ self.gauss = GaussCalc(self.py_script_path, self.work_path)
2009
+ self.step0_opt_freq_calc() # todo 4: to determine whether the initialize should be put at her
2010
+ else:
2011
+ # raise error on Environment not complete
2012
+ pass
2013
+ elif self.start_point == 1:
2014
+ if EnvCheck.check_gaussian() and EnvCheck.check_mlatom():
2015
+ self.step1_multiple_xyz_read_calc()
2016
+ else:
2017
+ # raise error on Environment not complete
2018
+ pass
2019
+ elif self.start_point == 2: # todo 4: to determined whether calc cross-section without ML
2020
+ if EnvCheck.check_mlatom():
2021
+ self.step2_grep_gauss_log()
2022
+ else:
2023
+ # raise error
2024
+ pass
2025
+ elif self.start_point == 3:
2026
+ self.step3_trans_user_E_f_file() # todo 4: to determined whether calc cross-section without ML
2027
+ else:
2028
+ # raise start point error
2029
+ pass
2030
+
2031
+ def step0_opt_freq_calc(self):
2032
+ if self.gauss_opt_inp_exist:
2033
+ self.cp(self.gauss_opt_inp_file, os.path.join(self.gauss.temp_path, 'opt.com') )
2034
+ self.cd(self.gauss.temp_path)
2035
+ self.gauss.gaussian_opt_calc()
2036
+ else:
2037
+ self.init_molecule.read_xyz(self.xyz_file, self.au_unit)
2038
+ self.gauss.gaussain_opt(molecule=self.init_molecule, functools=self.functionals, basis=self.basis,
2039
+ cores=self.cpu_cores, memory=self.memory_limit,
2040
+ charge=self.charge, multiplicity=self.multiplicity)
2041
+ # to check whehther TD_inp file is usable
2042
+ if self.gauss_calc_inp_exist:
2043
+ if not self.gauss.parse_gauss_inp_file(os.path.join(self.work_path, self.gauss_calc_inp_file)):
2044
+ # customise gaussian TD in Gaussian class
2045
+ # todo 4: raise error on parse gauss TD inp
2046
+ pass
2047
+ self.opt_molecule = self.gauss.extract_xyz(os.path.join(self.gauss.gauss_path, 'opt.log'))
2048
+ self.opt_molecule.print_xyz(os.path.join(self.cs_ML.ML_path, 'eq.xyz'))
2049
+ self.step0_1_NX_MD()
2050
+
2051
+ def step0_1_NX_MD(self):
2052
+ self.nx.cd(self.nx.NX_path)
2053
+ self.opt_molecule.print_xyz('opt.xyz')
2054
+ self.cp(os.path.join(self.gauss.gauss_path, 'opt.log'), self.nx.NX_path)
2055
+ self.nx.invoke_NX('opt.xyz', 'opt.log', self.opt_molecule.atom_num, self.N_ML_NEA_point)
2056
+ self.nx.cd(self.nx.NX_path)
2057
+ # todo 2: to remove other temporary file # DEBUG TEMP final_output freq.out geom initqp_input opt.xyz samp_param samp_points
2058
+ self.multi_xyz = self.nx.multi_xyz
2059
+ self.cd(self.work_path)
2060
+ self.step0_2_NX_xyz_gauss_calc()
2061
+
2062
+ @logger('calc all the TD point')
2063
+ def calc_all_TD_point(self):
2064
+ for idx, mole in enumerate(self.multi_xyz):
2065
+ if self.gauss_calc_inp_exist:
2066
+ self.gauss.gaussian_TD_calc_with_template(idx, mole)
2067
+ else:
2068
+ self.gauss.gaussain_TD_with_para(idx, mole, self.functionals, self.basis, self.N_max_state)
2069
+ # self.gauss.gaussain_TD_calc(idx, mole, self.calc_functionals, self.calc_basis, self.N_excit_state)
2070
+
2071
+ def step0_2_NX_xyz_gauss_calc(self):
2072
+ # todo 3: calc SPC spectra
2073
+ if self.use_all_point:
2074
+ self.calc_all_TD_point()
2075
+ self.gauss.process_gaussian_log(self.gauss.all_log_path, self.N_max_state, pattern='', regex=False)
2076
+ self.cs_TD.calc(self.gauss.gauss_data_path, self.N_max_state, self.TD_delta)
2077
+ self.cs_ML.define_data_set(self, self.N_TD_NEA_point, 'xyz.dat', self.N_max_state, self.ML_delta)
2078
+ self.cs_ML.ML_train_all()
2079
+ else:
2080
+ self.cs_ML.define_data_set(self, self.N_TD_NEA_point, 'xyz.dat', self.N_max_state, self.ML_delta)
2081
+ # todo 5: in test, use start=5, incr=2
2082
+ self.cs_ML.ML_train_iter(start_point=self.train_start_point, incr_point=self.train_incr_point, threshold=self.rRMSE_threshould)
2083
+ self.cs_TD.calc(self.gauss.gauss_data_path, self.N_max_state, self.TD_delta)
2084
+ opt_E_list, opt_f_list = self.gauss.process_one_gauss_log(os.path.join(self.gauss.all_log_path, '0.log'))
2085
+ self.cs_TD.calc_SPC(opt_E_list, opt_f_list, 0.5, 'cross-section-SPC.dat')
2086
+ self.step0_3_draw()
2087
+
2088
+ def step0_3_draw(self):
2089
+ self.cd(self.work_path)
2090
+ plot = Plot()
2091
+ plot.set_info('Energy, eV', r'Cross section, $\mathrm{\AA^2}$ molecule$^{–1}$', 'cross-section for QC-NEA', grid=True)
2092
+ plot.plot_file(self.cs_TD.TD_out_name, 'TD-cross-section.png', x_column=1, y_column=3)
2093
+ plot = Plot()
2094
+ plot.set_info('Energy, eV', r'Cross section, $\mathrm{\AA^2}$ molecule$^{–1}$', 'cross-section for ML-NEA', grid=True)
2095
+ # plot.set_info('Energy / eV', 'cross-section', 'cross-section for ML-NEA', grid=True)
2096
+ plot.plot_file(self.cs_ML.ML_out_name, 'ML-cross-section.png', x_column=1, y_column=3)
2097
+ plot = Plot()
2098
+ plot.set_info('Energy, eV', r'Cross section, $\mathrm{\AA^2}$ molecule$^{–1}$', 'cross-section for QC-SPC', grid=True)
2099
+ plot.plot_file(self.cs_TD.SPC_out_name, 'SPC-cross-section.png', x_column=1, y_column=3)
2100
+
2101
+
2102
+ def step1_multiple_xyz_read_calc(self):
2103
+ # to check whehther TD_inp file is usable
2104
+ if self.gauss_calc_inp_exist:
2105
+ if not self.gauss.parse_gauss_inp_file(os.path.join(self.work_path, self.gauss_calc_inp_file)):
2106
+ # customise gaussian TD in Gaussian class
2107
+ # todo 4: raise error on parse gauss TD inp
2108
+ pass
2109
+ self.step0_2_NX_xyz_gauss_calc()
2110
+
2111
+ def step2_grep_gauss_log(self):
2112
+ gauss_folder_path = self.ask(idx=2.0, prompt='define the folder of multiple gauss output file',
2113
+ description='Please input the folder path where stores the multiple gaussian output file',
2114
+ default='gauss_log', require_type=str)
2115
+ gauss_log_pattern_type = self.ask(idx=2.1, prompt='define the pattern type of gaussian log file, you can choose globing ' + \
2116
+ 'or regular expression.', default='0', require_type=str, upper_lmt=1, lower_lmt=0,
2117
+ description='you can input 0(globing pattern) or 1(regular expression)')
2118
+ self.ask_use_all_point(2.2)
2119
+ self.ask_state_NEA(2.3, 2.4)
2120
+ if gauss_log_pattern_type == 0:
2121
+ # globing
2122
+ gauss_log_pattern = self.ask(idx=2.5, prompt='define the globing pattern of gaussian log file', default='*.log',
2123
+ require_type=str, description='please input the globing pattern, "*" stands for any string, ' + \
2124
+ '"?" stands for any one character, you can also input nothing, and it will use all the file in the folder')
2125
+ self.gauss.process_gaussian_log(gauss_folder_path, self.N_excit_state, gauss_log_pattern, regex=False)
2126
+ elif gauss_log_pattern_type == 1:
2127
+ # regualr expression
2128
+ gauss_log_pattern = self.ask(idx=2.5, prompt='define the regular expression of gaussian log file', default=r'[0-9]+?\.log',
2129
+ require_type=str, description='please input the regular expression pattern, ' + \
2130
+ ' "*" stands for any string, "?" stands for any one character')
2131
+ self.gauss.process_gaussian_log(gauss_folder_path, self.N_excit_state, gauss_log_pattern, regex=True)
2132
+ else:
2133
+ # todo 3: to raise a error
2134
+ pass
2135
+ # todo 5: get multi-xyz variable from gauss log, the same for fN_excit_state, N_TD_NEA
2136
+ # todo 5: ask for the ML iteration or train all
2137
+ pass
2138
+
2139
+ def step3_trans_user_E_f_file(self):
2140
+ self.ask_use_all_point(3.0)
2141
+ self.ask_state_NEA(3.1, 3.2)
2142
+ data_path = self.ask(idx=3.3, prompt='please input the folder of E and f file', default='data', require_type=str,
2143
+ description='Please ensure the file name is E1.dat ... E9.dat and f1.dat ... f9.dat')
2144
+ self.cp(data_path + '/*', self.gauss.gauss_data_path)
2145
+ # todo 5: get multi-xyz variable from gauss log, the same for fN_excit_state, N_TD_NEA
2146
+ # todo 5: ask for the ML iteration or train all
2147
+
2148
+
2149
+ class ArgParser():
2150
+ def __init__(self) -> None:
2151
+ self.parser = argparse.ArgumentParser()
2152
+ self.set_arg()
2153
+ self.args = self.parser.parse_args()
2154
+
2155
+ def set_arg(self) -> None:
2156
+ self.parser.add_argument('-Nexcitations', default=3, type=int, help='define how many excited state you want to calculate. (default=3)')
2157
+ self.parser.add_argument('-nQMpoints', default=0, type=int, help='define the point you want to directly calculate with ML-NEA method. (default=0, will not calculate all point)')
2158
+ self.parser.add_argument('-nMaxPoints', default=10000, type=int, help='define the max QC calculation point in the ML procedure. (default=10000)')
2159
+ self.parser.add_argument('-MLpoints', default=50000, type=int, help='define how many point you want to predict with ML. (default=50000)')
2160
+ self.parser.add_argument('-plotQCNEA', action='store_true', help='define whether to draw QC-NEA plot, default=False')
2161
+ self.parser.add_argument('-plotQCSPC', action='store_true', help='define whether to draw QC-SPC plot, default=False')
2162
+ self.parser.add_argument('-deltaQCNEA', default=0.01, type=float, help='define the delta value you want to use in QC-NEA. (default=0.01)')
2163
+ # self.parser.add_argument('-useAllPoint', default=False, type=bool, help='if True, will use all NX point to calc, or will run it iterately(default)')
2164
+
2165
+
2166
+ class CtlPartAdapter(LinuxInterface):
2167
+ paper_adapter: int = 1
2168
+ custom_adapter: int = 2
2169
+ def __init__(self, handler: Union['RecurPaperProcedure', 'ProcedureCtl'], adapter_type: int):
2170
+ self.adapter: Union['RecurPaperProcedure', 'ProcedureCtl'] = handler
2171
+ self.type = adapter_type
2172
+
2173
+ def recheck_QC_calc(self, start: int, end: int) -> Tuple[bool, int, int]:
2174
+ if end < start:
2175
+ err_print('inner error: iteratively calc TD encountered error end < start')
2176
+ exit()
2177
+ if self.type == self.paper_adapter: # need to check whether it has E*.dat and f*.dat
2178
+ handle: RecurPaperProcedure = self.adapter
2179
+ if handle.exist_E_f_data: # exists E f data # end is not included
2180
+ if start > handle.E_f_max_line: # max [start end)
2181
+ return True, start, end
2182
+ elif start <= handle.E_f_max_line < end: # [start max end)
2183
+ return True, handle.E_f_max_line, end
2184
+ else:
2185
+ return False, start, end
2186
+ else: # not exists E*.dat f*.dat
2187
+ return True, start, end
2188
+ elif self.type == self.custom_adapter:
2189
+ return True, start, end
2190
+
2191
+ @property
2192
+ def gauss(self):
2193
+ return self.adapter.gauss
2194
+
2195
+ @property
2196
+ def multi_xyz(self):
2197
+ return self.adapter.multi_xyz
2198
+
2199
+ @property
2200
+ def gauss_calc_inp_exist(self) -> bool:
2201
+ if self.type == self.paper_adapter:
2202
+ paper: RecurPaperProcedure = self.adapter
2203
+ return self.exist_path(os.path.join(paper.work_path, paper.gauss_TD_inp_name))
2204
+ elif self.type == self.custom_adapter:
2205
+ custom: ProcedureCtl = self.adapter
2206
+ return custom.gauss_calc_inp_exist
2207
+
2208
+ @property
2209
+ def functionals(self) -> str:
2210
+ if self.type == self.paper_adapter:
2211
+ err_print('error invoke by paper adapter, exit!')
2212
+ exit()
2213
+ elif self.type == self.custom_adapter:
2214
+ custom: ProcedureCtl = self.adapter
2215
+ return custom.functionals
2216
+
2217
+ @property
2218
+ def basis(self) -> str:
2219
+ if self.type == self.paper_adapter:
2220
+ err_print('error invoke by paper adapter, exit!')
2221
+ exit()
2222
+ elif self.type == self.custom_adapter:
2223
+ custom: ProcedureCtl = self.adapter
2224
+ return custom.basis
2225
+
2226
+ @property
2227
+ def N_max_state(self) -> int:
2228
+ if self.type == self.paper_adapter:
2229
+ paper: RecurPaperProcedure = self.adapter
2230
+ return paper.max_state
2231
+ elif self.type == self.custom_adapter:
2232
+ custom: ProcedureCtl = self.adapter
2233
+ return custom.N_max_state
2234
+
2235
+
2236
+ class RecurPaperProcedure(ArgParser, LinuxInterface):
2237
+ # ╭─────────────────────────────────────────────────────────────────────────────────────────────────╮
2238
+ # │ ◎ ○ ○ ░░░░░░░░░░░░░░░░░░░░░░░░░ Control Part for Benzene 50k point ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
2239
+ # ├─────────────────────────────────────────────────────────────────────────────────────────────────┤
2240
+ # │ │
2241
+ # │ .───────────────. .───────────────. │
2242
+ # │ ┌─yes──( has xyz.dat )──no──┐ ┌──yes──( has opt.log )───no─┐ │
2243
+ # │ │ `───────────────' │ │ `───────────────' │ │
2244
+ # │ │ │ │ ▼ │
2245
+ # │ │ │ │ .───────────────. │
2246
+ # │ │ ▼ │ ┌──no───( has opt.com ) │
2247
+ # │ │ .─────────────. │ │ `───────────────' │
2248
+ # │ │ ┌─────────( NX-MD calc ) │ ▼ │ │
2249
+ # │ │ │ `─────────────' │ .───────────────. yes │
2250
+ # │ │ │ ▲ │ ( error, exit ) │ │
2251
+ # │ │ │ │ │ `───────────────' ▼ │
2252
+ # │ │ │ │ │ .─────────────. │
2253
+ # │ ┌ ─ ─ ┼ ─ ─ ─ ┐ │ │ │ ( run opt.com ) │
2254
+ # │ ▼ │ │ ▼ `─────────────' │
2255
+ # │ │ .───────. │ │ │ .───────────────. │ │
2256
+ # │ ( ML-NEA )◀───────┘ └────( get opt freq )◀──────────────────────┘ │
2257
+ # │ │ `───────' │ `───────────────' │
2258
+ # │ ▲ │ │
2259
+ # │ └ ─ ─ ┼ ─ ─ ─ ┘ │ │
2260
+ # │ └─────────────────────────────────────────────┘ │
2261
+ # │ │
2262
+ # └─────────────────────────────────────────────────────────────────────────────────────────────────┘
2263
+ def __init__(self) -> None:
2264
+ pass
2265
+
2266
+ def check_env(self) -> None:
2267
+ pass
2268
+
2269
+ def calc(self) -> None:
2270
+ # self.QC_calc_pre_check()
2271
+ self.detect_E_f_and_calc()
2272
+ self.opt_freq_calc()
2273
+ self.NX_multi_xyz_calc()
2274
+ if self.use_all_point:
2275
+ self.ML_NEA_calc_all()
2276
+ else:
2277
+ self.ML_NEA_calc_iter()
2278
+ self.QC_cross_section_calc()
2279
+ # self.draw_cs() # todo 5: add environment check
2280
+
2281
+ def invoke_by_self(self):
2282
+ super().__init__()
2283
+ self.pre_define()
2284
+ ##### args #####
2285
+ self.max_state: int = self.args.Nexcitations
2286
+ self.TD_point: int = self.args.nMaxPoints
2287
+ self.ML_point: int = self.args.MLpoints
2288
+ if self.args.nQMpoints:
2289
+ self.use_all_point: bool = True
2290
+ self.TD_point = self.args.nQMpoints
2291
+ else:
2292
+ self.use_all_point: bool = False
2293
+ #self.use_all_point: bool = self.args.useAllPoint
2294
+ self.delta_QC = self.args.deltaQCNEA
2295
+ self.calc()
2296
+ plotQCNEA = self.args.plotQCNEA
2297
+ plotQCSPC = self.args.plotQCSPC
2298
+ if not EnvCheck.check_matplotlib():
2299
+ err_print('not exists matplotlib module, will not plot!')
2300
+ else:
2301
+ self.draw_cs(plotQCNEA=plotQCNEA, plotQCSPC=plotQCSPC)
2302
+ print_citation()
2303
+
2304
+ def invoke_by_API(self, Nexcitations: int=3, nMaxPoints: int=10000, nNEApoints: int=0, MLpoints: int=500, deltaQCNEA: float=0.05):
2305
+ #print_citation()
2306
+ self.pre_define()
2307
+ ##### args #####
2308
+ self.max_state: int = Nexcitations
2309
+ self.TD_point: int = nMaxPoints
2310
+ self.ML_point: int = MLpoints
2311
+ if nNEApoints:
2312
+ self.use_all_point: bool = True
2313
+ self.TD_point = nNEApoints
2314
+ else:
2315
+ self.use_all_point: bool = False
2316
+ self.delta_QC = deltaQCNEA
2317
+ self.calc()
2318
+
2319
+ def pre_define(self) -> None:
2320
+ ##### file existance #####
2321
+ self.work_path = os.getcwd()
2322
+ self.py_script_path = os.path.dirname(os.path.abspath(__file__)) #self.py_script_path = os.path.abspath(__file__[:__file__.rfind('/')])
2323
+ global py_script_path
2324
+ py_script_path = self.py_script_path
2325
+ self.gauss_opt_inp_name: str = 'gaussian_optfreq.com' # self.gauss_opt_inp_name: str = 'opt-freq.com'
2326
+ self.gauss_TD_inp_name: str = 'gaussian_ef.com' # self.gauss_TD_inp_name: str = 'TD.com'
2327
+ self.gauss_opt_log_name: str = 'gaussian_optfreq.log' # self.gauss_opt_log_name: str = 'opt-freq.log'
2328
+ self.nx_nea_geom_name: str = 'nea_geoms.xyz' # self.nx_nea_geom_name: str = 'xyz.dat'
2329
+ self.eq_init_xyz_name: str = 'eq.xyz'
2330
+ self.eq_xyz_name: str = 'eq.xyz'
2331
+ self.E_files_name: str = 'E%s.dat'
2332
+ self.f_files_name: str = 'f%s.dat'
2333
+ self.exist_E_f_data: bool = False
2334
+ self.E_f_max_line: int = 0
2335
+ ##### calc handler class #####
2336
+ self.gauss = GaussCalc(self.py_script_path, self.work_path)
2337
+ self.nx = NewtonXCalc(self.work_path)
2338
+ self.cs_TD = TD_cross_section(self.work_path)
2339
+ self.cs_ML = ML_cross_section(self.work_path, self.py_script_path)
2340
+ self.cs_SPC = SPC_cross_section()
2341
+ ##### intermediate variable #####
2342
+ self.opt_molecule: Molecule = Molecule()
2343
+ self.multi_xyz: List[Molecule] = []
2344
+ ##### cross section parameter #####
2345
+ self.delta_ML = 0.01
2346
+ self.delta_QC = 0.01
2347
+ self.delta_SPC = 0.3
2348
+
2349
+ def opt_freq_calc(self):
2350
+ self.cd(self.work_path)
2351
+ if self.exist_path(self.eq_init_xyz_name) and self.exist_path(self.nx_nea_geom_name):
2352
+ self.opt_molecule.read_xyz(self.eq_init_xyz_name)
2353
+ return
2354
+ if not EnvCheck.check_NX():
2355
+ exit()
2356
+ self.cd(self.work_path)
2357
+ if not self.exist_path(self.gauss_opt_log_name): # no exists opt-freq.log
2358
+ if self.exist_path(self.gauss_opt_inp_name): # exists opt-freq.com, will calc it
2359
+ if not EnvCheck.check_gaussian():
2360
+ exit()
2361
+ self.gauss.gaussian_opt_calc(self.gauss_opt_inp_name)
2362
+ else: # not exists opt-freq.com, raise error
2363
+ err_print(f'not exists {self.gauss_opt_inp_name} or {self.gauss_opt_log_name}, exit')
2364
+ exit()
2365
+ # now exists opt-freq.log, extract opt.xyz & freq
2366
+ self.opt_molecule = self.gauss.extract_xyz(
2367
+ os.path.join(self.gauss.gauss_path, self.gauss_opt_log_name))
2368
+ # self.opt_molecule.print_xyz(self.eq_xyz_name) # no needed, don't pollute the working directory
2369
+
2370
+ def NX_multi_xyz_calc(self):
2371
+ self.cd(self.work_path)
2372
+ if not self.exist_path(self.nx_nea_geom_name): # not exist multiple xyz.dat, need to calc it with NX
2373
+ self.cd(self.nx.NX_path)
2374
+ self.opt_molecule.print_xyz('opt.xyz')
2375
+ self.cp(os.path.join(self.gauss.gauss_path, self.gauss_opt_log_name),
2376
+ os.path.join(self.nx.NX_path, 'opt.log'))
2377
+ if not EnvCheck.check_NX():
2378
+ exit()
2379
+ self.nx.invoke_NX('opt.xyz', 'opt.log', self.opt_molecule.atom_num, self.ML_point)
2380
+ self.multi_xyz = self.nx.multi_xyz
2381
+ else:
2382
+ # now exists xyz file, process this file and store it to self.multi_xyz_geom
2383
+ self.multi_xyz = Molecule.read_multi_xyz(self.nx_nea_geom_name)
2384
+ # self.ML_point = len(self.multi_xyz) - 1 # keep the MLpoint the same with ML-NEA (except the equibrium geom)
2385
+ if len(self.multi_xyz) - 1 >= self.ML_point:
2386
+ self.multi_xyz = self.multi_xyz[:self.ML_point + 1]
2387
+
2388
+ def ML_NEA_calc_all(self):
2389
+ self.cd(self.cs_ML.ML_path)
2390
+ self.opt_molecule.print_xyz(self.eq_xyz_name)
2391
+ # for idx, mole in enumerate(self.multi_xyz):
2392
+ # self.gauss.gaussian_TD_calc_with_template(idx, mole)
2393
+ # self.gauss.process_gaussian_log(self.gauss.all_log_path, self.max_state, pattern='', regex=False)
2394
+ ctl_adapter = CtlPartAdapter(self, CtlPartAdapter.paper_adapter)
2395
+ self.cs_ML.define_data_set(this=ctl_adapter, TD_point=self.TD_point, xyz_file=self.nx_nea_geom_name,
2396
+ max_state=self.max_state, delta=0.01)
2397
+ self.cs_ML.ML_train_all()
2398
+
2399
+ def ML_NEA_calc_iter(self):
2400
+ self.cd(self.cs_ML.ML_path)
2401
+ self.opt_molecule.print_xyz(self.eq_xyz_name)
2402
+ ctl_adapter = CtlPartAdapter(self, CtlPartAdapter.paper_adapter)
2403
+ self.cs_ML.define_data_set(this=ctl_adapter, TD_point=self.TD_point, xyz_file=self.nx_nea_geom_name,
2404
+ max_state=self.max_state, delta=self.delta_ML)
2405
+ # self.cs_ML.ML_train_iter(start_point=5, incr_point=2, threshold=0.2) # threshold=0.1
2406
+ self.cs_ML.ML_train_iter(start_point=50, incr_point=50, threshold=0.1) # threshold=0.1
2407
+
2408
+ def QC_calc_pre_check(self):
2409
+ self.cd(self.work_path)
2410
+ if not self.gauss.parse_gauss_inp_file(self.gauss_TD_inp_name): # this procedure will also make a template for TD calculation
2411
+ err_print(f'has error on {self.gauss_TD_inp_name}, exit!')
2412
+ exit()
2413
+
2414
+ def QC_cross_section_calc(self):
2415
+ self.cd(self.cs_TD.cs_path)
2416
+ self.cs_TD.calc(self.gauss.gauss_data_path, self.max_state, self.delta_QC, self.cs_TD.TD_out_name) # use default delta=0.05
2417
+ opt_E_list, opt_f_list = self.gauss.process_one_gauss_log(os.path.join(self.gauss.all_log_path, '0.log'))
2418
+ self.opt_E_list, self.opt_f_list = opt_E_list, opt_f_list
2419
+ # self.cs_TD.calc_SPC(opt_E_list, opt_f_list, self.delta_SPC, self.cs_TD.SPC_out_name)
2420
+ self.cs_SPC.read_data_from_para(opt_E_list, opt_f_list)
2421
+ SPC_delta = 0.3
2422
+ x_min = min(self.cs_TD.x_min, self.cs_ML.x_min) - SPC_delta / 2
2423
+ x_max = max(self.cs_TD.x_max, self.cs_ML.x_max) + SPC_delta / 2
2424
+ self.cs_SPC.set_para(delta=SPC_delta, x_min=x_min, x_max=x_max)
2425
+ self.cs_SPC.calc(self.cs_SPC.SPC_out_name)
2426
+
2427
+ def draw_cs(self, plotQCNEA: bool=True, plotQCSPC: bool=True): # todo 4: plot in a figure
2428
+ qc_nea_name = 'cross-section_qc-nea.png'
2429
+ ml_nea_name = 'cross-section_ml-nea.png'
2430
+ spc_nea_name = 'cross-section_spc.png'
2431
+ self.cd(self.cs_ML.cs_path)
2432
+ if False:
2433
+ plot = Plot()
2434
+ plot.set_info('Energy, eV', r'Cross section, $\mathrm{\AA^2}$ molecule$^{–1}$', 'cross-section for QC-NEA', grid=True)
2435
+ plot.plot_file(self.cs_TD.TD_out_name, qc_nea_name, x_column=1, y_column=3)
2436
+ # plot.plot_file(self.cs_TD.TD_out_name, 'TD-cross-section.png', x_column=1, y_column=3)
2437
+ plot = Plot()
2438
+ plot.set_info('Energy, eV', r'Cross section, $\mathrm{\AA^2}$ molecule$^{–1}$', 'cross-section for ML-NEA', grid=True)
2439
+ plot.plot_file(self.cs_ML.ML_out_name, ml_nea_name, x_column=1, y_column=3)
2440
+ # plot.plot_file(self.cs_ML.output_file_name, 'ML-cross-section.png', x_column=1, y_column=3)
2441
+ plot = Plot()
2442
+ plot.set_info('Energy, eV', r'Cross section, $\mathrm{\AA^2}$ molecule$^{–1}$', 'cross-section for QC-SPC', grid=True)
2443
+ plot.plot_file(self.cs_TD.SPC_out_name, spc_nea_name, x_column=1, y_column=3)
2444
+ # plot.plot_file(self.cs_TD.SPC_out_name, 'SPC-cross-section.png', x_column=1, y_column=3)
2445
+ else:
2446
+ file_names: List[str] = []
2447
+ legend_names: List[str] = []
2448
+ colors: List[str] = []
2449
+ plot = Plot()
2450
+ if self.exist_path(os.path.join(self.work_path, 'cross-section_ref.dat')):
2451
+ file_names.append('cross-section_ref.dat')
2452
+ legend_names.append('ref')
2453
+ colors.append('k')
2454
+ self.cp(os.path.join(self.work_path, 'cross-section_ref.dat'), self.cs_ML.cs_path)
2455
+ if self.exist_path(os.path.join(self.cs_ML.cs_path, self.cs_TD.TD_out_name)) and plotQCNEA:
2456
+ file_names.append(self.cs_TD.TD_out_name)
2457
+ legend_names.append('QC-NEA')
2458
+ colors.append('b')
2459
+ if self.exist_path(os.path.join(self.cs_ML.cs_path, self.cs_ML.ML_out_name)):
2460
+ file_names.append(self.cs_ML.ML_out_name)
2461
+ legend_names.append('ML-NEA')
2462
+ colors.append('r')
2463
+ if self.exist_path(os.path.join(self.cs_ML.cs_path, self.cs_SPC.SPC_out_name)) and plotQCSPC:
2464
+ file_names.append(self.cs_SPC.SPC_out_name)
2465
+ legend_names.append('QC-SPC')
2466
+ colors.append('g')
2467
+ plot.plot_spc_bar(self.opt_E_list, self.opt_f_list)
2468
+ # plot.ax.set_xlim(min(self.opt_E_list), max(self.opt_E_list))
2469
+ if False:
2470
+ if self.exist_path(os.path.join(self.work_path, 'cross-section_ref.dat')):
2471
+ # file_names.append('cross-section_ref.dat')
2472
+ file_names = ['cross-section_ref.dat'] + file_names
2473
+ # legend_names.append('ref-NEA')
2474
+ legend_names = ['ref-NEA'] + legend_names
2475
+ # colors.append('k')
2476
+ colors = ['k'] + colors
2477
+ self.cp(os.path.join(self.work_path, 'cross-section_ref.dat'), self.cs_ML.cs_path)
2478
+ file_names = [self.cs_TD.TD_out_name, self.cs_ML.ML_out_name, self.cs_TD.SPC_out_name]
2479
+ legend_names = ['QC-NEA', 'ML-NEA', 'QC-SPC']
2480
+ colors = ['b', 'r', 'g']
2481
+ if not file_names:
2482
+ err_print('not caluclated cross-section file exists. Exit!')
2483
+ exit()
2484
+ plot.set_info(xlabel='Energy, eV', ylabel=r'Cross section, $\mathrm{\AA^2}$ molecule$^{–1}$', grid=False)
2485
+ plot.plot_multiple_file(file_names, legend_names, colors, x_column=1, y_column=3)
2486
+ plot.ax.set_ylim(bottom=0.0)
2487
+ plot.ax.margins(x=0.0)
2488
+ plot.save_fig('plot.png')
2489
+
2490
+ def detect_E_f_and_calc(self):
2491
+ for i in range(1, self.max_state + 1):
2492
+ if (not self.exist_path(f'E{i}.dat')) or (not self.exist_path(f'f{i}.dat')):
2493
+ # err_print('E*.dat and f*.dat is not compatible with max_excited_state')
2494
+ self.QC_calc_pre_check()
2495
+ return
2496
+ self.exist_E_f_data = True
2497
+ self.fabricate_gauss_data()
2498
+
2499
+ def fabricate_gauss_data(self): # the 0th should be the equilibrium geom
2500
+ def read_line_data(file_name: str):
2501
+ with open(file_name) as f:
2502
+ data = f.read().splitlines()
2503
+ return list(map(float, data))
2504
+
2505
+ def fabricate_gauss_log(idx: int, Es: List[float], fs: List[float]):
2506
+ with open(os.path.join(self.gauss.all_log_path, f'{idx}.log'), 'w') as file:
2507
+ for i, E in enumerate(Es):
2508
+ f = fs[i]
2509
+ file.write(template % (i + 1, E, f) + '\n')
2510
+
2511
+ template = ' Excited State %d: Singlet- %f eV 0.000 nm f=%f <S**2>=0.000'
2512
+ E_data = []; f_data = []
2513
+ for i in range(1, self.max_state + 1):
2514
+ E_data.append(read_line_data(f'E{i}.dat'))
2515
+ f_data.append(read_line_data(f'f{i}.dat'))
2516
+ self.E_f_max_line = len(E_data[0])
2517
+ if len(E_data[0]) != len(f_data[0]): # todo 3: need to check every file's line
2518
+ err_print('E*.dat and f*.dat doesn\'t match, exit!')
2519
+ exit()
2520
+ for idx, _ in enumerate(E_data[0]):
2521
+ Es = [ x[idx] for x in E_data]
2522
+ fs = [ x[idx] for x in f_data]
2523
+ fabricate_gauss_log(idx, Es, fs)
2524
+
2525
+
2526
+ Nexcitations_str = 'Nexcitations'
2527
+ nQMpoints_str = 'nQMpoints'
2528
+ nMaxPoints_str = 'nMaxPoints'
2529
+ MLpoints_str = 'MLpoints'
2530
+ deltaQCNEA_str = 'deltaQCNEA'
2531
+
2532
+
2533
+ class ControlPartProxy():
2534
+ def __init__(self):
2535
+ self.start_benzene_50k_ML_calc()
2536
+ # judge this execution is define work input file or directly execute
2537
+ # define work need some special argument with the execution command
2538
+ @classmethod
2539
+ @logger('Main procedure starts')
2540
+ def invoke_paper_recur_api(cls, Nexcitations: int=3, nMaxPoints: int=10000, nTDpoints: int=0, MLpoints: int=50000, deltaQCNEA: float=0.01, plotQCNEA: bool=False, plotQCSPC: bool=False):
2541
+ cwd = os.getcwd()
2542
+ Nexcitations = 3 if Nexcitations == 0 else Nexcitations
2543
+ nMaxPoints = 10000 if nMaxPoints == 0 else nMaxPoints
2544
+ MLpoints = 50000 if MLpoints == 0 else MLpoints
2545
+ deltaQCNEA = 0.01 if deltaQCNEA == 0.0 else deltaQCNEA
2546
+ handle = RecurPaperProcedure()
2547
+ handle.invoke_by_API(Nexcitations, nMaxPoints, nTDpoints, MLpoints, deltaQCNEA)
2548
+ if not EnvCheck.check_matplotlib():
2549
+ err_print('not exists matplotlib module, will not plot!')
2550
+ else:
2551
+ handle.draw_cs(plotQCNEA, plotQCSPC)
2552
+ print_citation()
2553
+ LinuxInterface.cd(cwd)
2554
+
2555
+ @classmethod
2556
+ @logger('Main procedure starts')
2557
+ def invoke_paper_recur_self(cls):
2558
+ handle = RecurPaperProcedure()
2559
+ handle.invoke_by_self()
2560
+
2561
+ @logger('Main procedure starts')
2562
+ def start_benzene_50k_ML_calc(self):
2563
+ RecurPaperProcedure()
2564
+ # super(Main, self).__init__()
2565
+
2566
+ @logger('Main procedure starts')
2567
+ def start_user_custom_calc(self):
2568
+ ProcedureCtl()
2569
+
2570
+ @classmethod
2571
+ def copyright_printer(cls):
2572
+ pass
2573
+
2574
+ def parse_api(args: List[str]):
2575
+
2576
+ def transfer(data: str, var_expr: str, require_type: type):
2577
+ try:
2578
+ tmp = require_type(data)
2579
+ return tmp
2580
+ except:
2581
+ err_print( '%s requires a %s type. exit!' % (var_expr, repr(type).split("'")[1]) )
2582
+ exit()
2583
+
2584
+ plotQCSPC: bool = False
2585
+ plotQCNEA: bool = False
2586
+ Nexcitations: int = 0
2587
+ nQMpoints: int = 0
2588
+ nMaxPoints: int = 0
2589
+ MLpoints: int = 0
2590
+ deltaQCNEA: float = 0.0
2591
+
2592
+ for arg in args:
2593
+ splited = arg.split('=')
2594
+ if len(splited) == 1:
2595
+ if splited[0].strip() == 'plotQCNEA':
2596
+ plotQCNEA = True
2597
+ elif splited[0].strip() == 'plotQCSPC':
2598
+ plotQCSPC = True
2599
+ elif splited[0].strip() in ['crossSection', 'cross-section', 'cross_section', 'MLNEA', 'ML-NEA', 'ML_NEA']:
2600
+ pass
2601
+ else:
2602
+ err_print('unrecognised parameter: %s. exit!' % splited[0])
2603
+ exit()
2604
+ if len(splited) == 2:
2605
+ if splited[0].strip().lower() == Nexcitations_str.lower():
2606
+ Nexcitations = transfer(splited[1], 'Nexcitations', int)
2607
+ elif splited[0].strip().lower() == nMaxPoints_str.lower():
2608
+ nMaxPoints = transfer(splited[1], 'nMaxPoints', int)
2609
+ elif splited[0].strip().lower() == nQMpoints_str.lower():
2610
+ nQMpoints = transfer(splited[1], 'nQMpoints', int)
2611
+ elif splited[0].strip().lower() in [MLpoints_str.lower(), 'nNEpoints'.lower()]:
2612
+ MLpoints = transfer(splited[1], 'nNEpoints', int)
2613
+ # elif splited[0].strip().lower() == MLpoints_str.lower():
2614
+ # MLpoints = transfer(splited[1], 'MLpoints', int)
2615
+ elif splited[0].strip().lower() == deltaQCNEA_str.lower():
2616
+ deltaQCNEA = transfer(splited[1], 'deltaQCNEA', float)
2617
+ else:
2618
+ err_print('unrecognised parameter: %s. exit!' % splited[0])
2619
+ exit()
2620
+ if len(splited) >= 3:
2621
+ err_print('has more than one "=" symbol, exit!')
2622
+ exit()
2623
+ ControlPartProxy.invoke_paper_recur_api(Nexcitations, nMaxPoints, nQMpoints, MLpoints, deltaQCNEA, plotQCNEA, plotQCSPC)
2624
+
2625
+ def print_help():
2626
+ help='''
2627
+ -------------------------------------------------------------------------------
2628
+ Options for ML-NEA:
2629
+ usage: MLatom.py cross-section [optional arguments]
2630
+
2631
+ optional arguments:
2632
+ nExcitations=N number of excited states to calculate. (default=3)
2633
+ nQMpoints=N user-defined number of QM calculations for training ML.
2634
+ (default=0, number of QM calculations will be determined iteratively)
2635
+ plotQCNEA requests plotting QC-NEA cross section
2636
+ deltaQCNEA=float define the broadening parameter of QC-NEA cross section
2637
+ plotQCSPC requests plotting cross section obtained via single point convolution
2638
+
2639
+ advanced arguments:
2640
+ nMaxPoints=N maximum number of QC calculations in the iterative procedure. (default=10000)
2641
+ nNEpoints=N number of ML ensemble prediction. (default=50000)
2642
+
2643
+ Environment setting (no needed)
2644
+ $NX Newton-X environment
2645
+ configured gaussian Gaussian environment (see Gaussian manual for more detail)
2646
+
2647
+ file that needs prepared
2648
+ 1. mandatory file
2649
+ gaussian_optfreq.com file that will execute gaussian opt and freq calc (alternatively with eq.xyz and nea_geoms.xyz)
2650
+ gaussian_ef.com template file that will execute QC calculations
2651
+
2652
+ 2. optional file
2653
+ cross-section_ref.dat reference cross-section file calculated with Newton-X
2654
+ eq.xyz optimized geometry file (to be used together with nea_geoms.xyz)
2655
+ nea_geoms.xyz file with all geometries in nuclear ensemble (to be used together with eq.xyz)
2656
+ E1.dat E2.dat ... files that store the excitation energy and oscillator strength
2657
+ f1.dat f2.dat ... per line which corresponds to nea_geoms.xyz
2658
+
2659
+
2660
+ output file
2661
+ cross-section/cross-section_ml-nea.dat cross-section spectra calculated with ML-NEA method
2662
+ cross-section/cross-section_qc-nea.dat cross-section spectra calculated with QC-NEA method
2663
+ cross-section/cross-section_spc.dat cross-section spectra calculated with single-point-convolution
2664
+ cross-section/plot.png the plot of cross-section calculated with different kinds of methods
2665
+
2666
+ citation for ML-NEA:
2667
+ B.-X. Xue, M. Barbatti, P. O. Dral, J. Phys. Chem. A 2020, 124, 7199. DOI: 10.1021/acs.jpca.0c05310
2668
+
2669
+ Since it uses Newton-X, please also cite Newton-X appropriately
2670
+ -------------------------------------------------------------------------------
2671
+ '''
2672
+ print(help)
2673
+
2674
+ def print_citation():
2675
+ citation = ' B.-X. Xue, M. Barbatti, P. O. Dral, J. Phys. Chem. A 2020, 124, 7199. DOI: 10.1021/acs.jpca.0c05310'
2676
+ nearef = ' R. Crespo-Otero, M. Barbatti, Theor. Chem. Acc. 2012, 131, 1237, DOI: 10.1007/s00214-012-1237-4'
2677
+ wignerref = ' Schinke, R. Photodissociation Dynamics: Spectroscopy and Fragmentation of Small Polyatomic Molecules; Cambridge University Press: Cambridge, U.K., 1995'
2678
+ cs_algorithm = ' M. Barbatti, G. Granucci, M. Ruckenbauer, F. Plasser, \n' \
2679
+ ' R. Crespo-Otero, J. Pittner, M. Persico, H. Lischka, NEWTON-X: \n' \
2680
+ ' a package for Newtonian dynamics close to the crossing seam, \n' \
2681
+ ' version 2.2, www.newtonx.org (2017). \n' \
2682
+ ' \n' \
2683
+ ' M. Barbatti, M. Ruckenbauer, F. Plasser, J. Pittner, G. Granucci, \n' \
2684
+ ' M. Persico, H. Lischka, WIREs: Comp. Mol. Sci., 4, 26 (2014).'
2685
+ print('*'*80)
2686
+ print(' You are going to use feature(s) listed below. \n Please cite corresponding work(s) in your paper:')
2687
+ print('\n %s:\n\t%s'%('ML-NEA',citation))
2688
+ print('\n %s:\n\t%s'%('NEA', nearef))
2689
+ print('\n %s:\n\t%s'%('Samling from a Wigner distribution',wignerref))
2690
+ print('\n %s:\n\t%s'%('NEWTON-X',cs_algorithm))
2691
+ print('*'*80)
2692
+
2693
+
2694
+ if __name__ == "__main__":
2695
+ ControlPartProxy.invoke_paper_recur_self()
2696
+ # ControlPartProxy.invoke_paper_recur_api(nTDpoints=200, plotQCNEA=True, deltaQCNEA=0.08)
2697
+