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.
- EGG-INFO/PKG-INFO +145 -0
- EGG-INFO/SOURCES.txt +40 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/native_libs.txt +1 -0
- EGG-INFO/not-zip-safe +1 -0
- EGG-INFO/requires.txt +2 -0
- EGG-INFO/scripts/MLatomF +12 -0
- EGG-INFO/scripts/mlatom +12 -0
- EGG-INFO/top_level.txt +1 -0
- MLatom/ML_NEA.py +2697 -0
- MLatom/MLatom.py +151 -0
- MLatom/MLatomF +0 -0
- MLatom/MLtasks.py +784 -0
- MLatom/__init__.py +0 -0
- MLatom/__pycache__/ML_NEA.cpython-37.pyc +0 -0
- MLatom/__pycache__/MLatom.cpython-37.pyc +0 -0
- MLatom/__pycache__/MLtasks.cpython-37.pyc +0 -0
- MLatom/__pycache__/__init__.cpython-37.pyc +0 -0
- MLatom/__pycache__/args_class.cpython-37.pyc +0 -0
- MLatom/__pycache__/doc.cpython-37.pyc +0 -0
- MLatom/__pycache__/header.cpython-37.pyc +0 -0
- MLatom/__pycache__/interface_MLatomF.cpython-37.pyc +0 -0
- MLatom/__pycache__/sliceData.cpython-37.pyc +0 -0
- MLatom/__pycache__/stopper.cpython-37.pyc +0 -0
- MLatom/args_class.py +396 -0
- MLatom/cs.so +0 -0
- MLatom/doc.py +620 -0
- MLatom/header.py +64 -0
- MLatom/interface_MLatomF.py +87 -0
- MLatom/interfaces/DeePMDkit/DP_inference.py +20 -0
- MLatom/interfaces/DeePMDkit/__init__.py +0 -0
- MLatom/interfaces/DeePMDkit/__pycache__/DP_inference.cpython-37.pyc +0 -0
- MLatom/interfaces/DeePMDkit/__pycache__/__init__.cpython-37.pyc +0 -0
- MLatom/interfaces/DeePMDkit/__pycache__/interface_DeePMDkit.cpython-37.pyc +0 -0
- MLatom/interfaces/DeePMDkit/interface_DeePMDkit.py +327 -0
- MLatom/interfaces/DeePMDkit/template.json +70 -0
- MLatom/interfaces/GAP/__init__.py +0 -0
- MLatom/interfaces/GAP/__pycache__/__init__.cpython-37.pyc +0 -0
- MLatom/interfaces/GAP/__pycache__/interface_GAP.cpython-37.pyc +0 -0
- MLatom/interfaces/GAP/interface_GAP.py +415 -0
- MLatom/interfaces/PhysNet/PhysNet_predict.py +180 -0
- MLatom/interfaces/PhysNet/PhysNet_train.py +425 -0
- MLatom/interfaces/PhysNet/__init__.py +0 -0
- MLatom/interfaces/PhysNet/__pycache__/PhysNet_predict.cpython-37.pyc +0 -0
- MLatom/interfaces/PhysNet/__pycache__/PhysNet_train.cpython-37.pyc +0 -0
- MLatom/interfaces/PhysNet/__pycache__/__init__.cpython-37.pyc +0 -0
- MLatom/interfaces/PhysNet/__pycache__/interface_PhysNet.cpython-37.pyc +0 -0
- MLatom/interfaces/PhysNet/interface_PhysNet.py +270 -0
- MLatom/interfaces/TorchANI/TorchANI_predict.py +82 -0
- MLatom/interfaces/TorchANI/TorchANI_train.py +217 -0
- MLatom/interfaces/TorchANI/__init__.py +0 -0
- MLatom/interfaces/TorchANI/__pycache__/TorchANI_predict.cpython-37.pyc +0 -0
- MLatom/interfaces/TorchANI/__pycache__/TorchANI_train.cpython-37.pyc +0 -0
- MLatom/interfaces/TorchANI/__pycache__/__init__.cpython-37.pyc +0 -0
- MLatom/interfaces/TorchANI/__pycache__/interface_TorchANI.cpython-37.pyc +0 -0
- MLatom/interfaces/TorchANI/interface_TorchANI.py +323 -0
- MLatom/interfaces/__init__.py +0 -0
- MLatom/interfaces/__pycache__/__init__.cpython-37.pyc +0 -0
- MLatom/interfaces/sGDML/__init__.py +0 -0
- MLatom/interfaces/sGDML/__pycache__/__init__.cpython-37.pyc +0 -0
- MLatom/interfaces/sGDML/__pycache__/interface_sGDML.cpython-37.pyc +0 -0
- MLatom/interfaces/sGDML/__pycache__/sgdml_dataset_from_extxyz.cpython-37.pyc +0 -0
- MLatom/interfaces/sGDML/interface_sGDML.py +276 -0
- MLatom/interfaces/sGDML/sgdml_dataset_from_extxyz.py +199 -0
- MLatom/ref.json +23 -0
- MLatom/sliceData.py +334 -0
- 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
|
+
|