yu-mcal 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcal/__init__.py +1 -0
- mcal/calculations/__init__.py +0 -0
- mcal/calculations/hopping_mobility_model.py +391 -0
- mcal/calculations/rcal.py +408 -0
- mcal/calculations/tcal.py +1123 -0
- mcal/constants/element_properties.csv +121 -0
- mcal/mcal.py +838 -0
- mcal/utils/__init__.py +0 -0
- mcal/utils/cif_reader.py +645 -0
- mcal/utils/gaus_log_reader.py +91 -0
- mcal/utils/gjf_maker.py +267 -0
- yu_mcal-0.1.0.dist-info/METADATA +257 -0
- yu_mcal-0.1.0.dist-info/RECORD +16 -0
- yu_mcal-0.1.0.dist-info/WHEEL +4 -0
- yu_mcal-0.1.0.dist-info/entry_points.txt +2 -0
- yu_mcal-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
"""Rcal"""
|
|
2
|
+
import argparse
|
|
3
|
+
import functools
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from time import time
|
|
9
|
+
from typing import List, Literal
|
|
10
|
+
|
|
11
|
+
from mcal.utils.cif_reader import CifReader
|
|
12
|
+
from mcal.utils.gjf_maker import GjfMaker
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
print = functools.partial(print, flush=True)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main():
|
|
19
|
+
"""This code is to execute rcal for command line."""
|
|
20
|
+
parser = argparse.ArgumentParser()
|
|
21
|
+
parser.add_argument('file', help='cif file name or gjf file name', type=str)
|
|
22
|
+
parser.add_argument('osc_type', help='organic semiconductor type', type=str)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
'-M', '--method',
|
|
25
|
+
help='calculation method used in Gaussian calculations (default is B3LYP/6-31G(d,p)). ' \
|
|
26
|
+
+ 'But if you use a gjf file instead of a cif file, the method in the gjf file will be used',
|
|
27
|
+
type=str,
|
|
28
|
+
default='B3LYP/6-31G(d,p)',
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
'-c', '--cpu',
|
|
32
|
+
help='setting the number of cpu (default is 4). ' \
|
|
33
|
+
+ 'But if you use a gjf file instead of a cif file, the number of cpu in the gjf file will be used',
|
|
34
|
+
type=int, default=4
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
'-m', '--mem',
|
|
38
|
+
help='setting the number of memory [GB] (default is 10 GB). ' \
|
|
39
|
+
+ 'But if you use a gjf file instead of a cif file, the number of memory in the gjf file will be used',
|
|
40
|
+
type=int,
|
|
41
|
+
default=10,
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument('-g', '--g09', help='use Gaussian 09 (default is Gaussian 16)', action='store_true')
|
|
44
|
+
parser.add_argument('-r', '--read', help='read log files without executing Gaussian', action='store_true')
|
|
45
|
+
args = parser.parse_args()
|
|
46
|
+
|
|
47
|
+
print('---------------------------------------')
|
|
48
|
+
print(' rcal beta (2025/06/21) by Matsui Lab. ')
|
|
49
|
+
print('---------------------------------------')
|
|
50
|
+
print(f'\nInput File Name: {args.file}')
|
|
51
|
+
Rcal.print_timestamp()
|
|
52
|
+
before = time()
|
|
53
|
+
|
|
54
|
+
if args.file.endswith('.cif'):
|
|
55
|
+
cif_file = Path(args.file)
|
|
56
|
+
directory = cif_file.parent
|
|
57
|
+
filename = cif_file.stem.replace('_opt_n', '')
|
|
58
|
+
file_path_without_ext = f'{directory}/{filename}_opt_n'
|
|
59
|
+
|
|
60
|
+
cif_reader = CifReader(cif_path=cif_file)
|
|
61
|
+
symbols = cif_reader.unique_symbols[0]
|
|
62
|
+
coordinates = cif_reader.unique_coords[0]
|
|
63
|
+
coordinates = cif_reader.convert_frac_to_cart(coordinates)
|
|
64
|
+
|
|
65
|
+
gjf_maker = GjfMaker()
|
|
66
|
+
gjf_maker.create_chk_file()
|
|
67
|
+
gjf_maker.output_detail()
|
|
68
|
+
gjf_maker.opt()
|
|
69
|
+
|
|
70
|
+
gjf_maker.set_symbols(symbols)
|
|
71
|
+
gjf_maker.set_coordinates(coordinates)
|
|
72
|
+
gjf_maker.set_function(args.method)
|
|
73
|
+
gjf_maker.set_charge_spin(charge=0, spin=1)
|
|
74
|
+
gjf_maker.set_resource(cpu_num=args.cpu, mem_num=args.mem)
|
|
75
|
+
|
|
76
|
+
gjf_maker.export_gjf(
|
|
77
|
+
file_name=f'{file_path_without_ext}',
|
|
78
|
+
chk_rwf_name=f'{file_path_without_ext}',
|
|
79
|
+
)
|
|
80
|
+
elif args.file.endswith('.gjf'):
|
|
81
|
+
gjf_file = Path(args.file)
|
|
82
|
+
directory = gjf_file.parent
|
|
83
|
+
filename = gjf_file.stem
|
|
84
|
+
|
|
85
|
+
file_path_without_ext = f'{directory}/{filename}'
|
|
86
|
+
else:
|
|
87
|
+
raise ValueError('Input file must be a cif file or a gjf file.')
|
|
88
|
+
|
|
89
|
+
if args.osc_type.lower() == 'p':
|
|
90
|
+
rcal = Rcal(gjf_file=f'{file_path_without_ext}.gjf')
|
|
91
|
+
elif args.osc_type.lower() == 'n':
|
|
92
|
+
rcal = Rcal(gjf_file=f'{file_path_without_ext}.gjf', osc_type='n')
|
|
93
|
+
else:
|
|
94
|
+
raise OSCTypeError
|
|
95
|
+
|
|
96
|
+
if args.g09:
|
|
97
|
+
gau_com = 'g09'
|
|
98
|
+
else:
|
|
99
|
+
gau_com = 'g16'
|
|
100
|
+
|
|
101
|
+
reorg_energy = rcal.calc_reorganization(gau_com=gau_com, only_read=args.read, is_output_detail=True)
|
|
102
|
+
|
|
103
|
+
print()
|
|
104
|
+
print('-----------------------')
|
|
105
|
+
print(' Reorganization energy ')
|
|
106
|
+
print('-----------------------')
|
|
107
|
+
print(f'{reorg_energy:12.6f} eV')
|
|
108
|
+
print()
|
|
109
|
+
|
|
110
|
+
Rcal.print_timestamp()
|
|
111
|
+
after = time()
|
|
112
|
+
print(f'Elapsed Time: {(after - before) * 1000:.0f} ms')
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Rcal:
|
|
116
|
+
"""Calculate organization energy."""
|
|
117
|
+
def __init__(self, gjf_file: str, osc_type: Literal['p', 'n'] = 'p'):
|
|
118
|
+
"""
|
|
119
|
+
Initialize Rcal.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
gjf_file : str
|
|
124
|
+
gjf file name.
|
|
125
|
+
osc_type : Literal['p', 'n']
|
|
126
|
+
organic semiconductor type, 'p' is positive, 'n' is negative, by default 'p'.
|
|
127
|
+
"""
|
|
128
|
+
self.gjf_file = gjf_file
|
|
129
|
+
self.ion = None
|
|
130
|
+
self._extension_log = '.log'
|
|
131
|
+
self._gjf_lines = {'%': [], '#': []}
|
|
132
|
+
|
|
133
|
+
self._input_gjf()
|
|
134
|
+
|
|
135
|
+
if osc_type.lower() == 'p':
|
|
136
|
+
self.ion = 'c'
|
|
137
|
+
elif osc_type.lower() == 'n':
|
|
138
|
+
self.ion = 'a'
|
|
139
|
+
|
|
140
|
+
@ staticmethod
|
|
141
|
+
def check_error_term(line: str) -> None:
|
|
142
|
+
"""
|
|
143
|
+
Check the error term of Gaussian.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
line : str
|
|
148
|
+
last line of the log file.
|
|
149
|
+
|
|
150
|
+
Raises
|
|
151
|
+
------
|
|
152
|
+
GausTermError
|
|
153
|
+
if the calculation of Gaussian was error termination.
|
|
154
|
+
"""
|
|
155
|
+
line = line.strip()
|
|
156
|
+
|
|
157
|
+
if 'Normal termination' not in line:
|
|
158
|
+
raise GausTermError('The calculation of Gaussian was error termination.')
|
|
159
|
+
|
|
160
|
+
@staticmethod
|
|
161
|
+
def print_timestamp() -> None:
|
|
162
|
+
"""Print timestamp."""
|
|
163
|
+
month = {
|
|
164
|
+
1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
|
|
165
|
+
7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec',
|
|
166
|
+
}
|
|
167
|
+
dt_now = datetime.now()
|
|
168
|
+
print(f"Timestamp: {dt_now.strftime('%a')} {month[dt_now.month]} {dt_now.strftime('%d %H:%M:%S %Y')}")
|
|
169
|
+
|
|
170
|
+
def calc_reorganization(
|
|
171
|
+
self,
|
|
172
|
+
gau_com: str = 'g16',
|
|
173
|
+
only_read: bool = False,
|
|
174
|
+
is_output_detail: bool = False,
|
|
175
|
+
skip_specified_cal: List[Literal['opt_neutral', 'opt_ion', 'neutral', 'ion']] = [],
|
|
176
|
+
) -> float:
|
|
177
|
+
"""
|
|
178
|
+
Calculate reorganization energy.
|
|
179
|
+
|
|
180
|
+
Parameters
|
|
181
|
+
----------
|
|
182
|
+
gau_com : str
|
|
183
|
+
Gaussian command, by default 'g16'.
|
|
184
|
+
only_read : bool
|
|
185
|
+
if True, the calculation is only read, by default False.
|
|
186
|
+
is_output_detail : bool
|
|
187
|
+
if True, the calculation detail will be output, by default False.
|
|
188
|
+
skip_specified_cal : List[Literal['opt_neutral', 'opt_ion', 'neutral', 'ion']]
|
|
189
|
+
if specified, the calculation of the specified type will be skipped, by default [].
|
|
190
|
+
|
|
191
|
+
Returns
|
|
192
|
+
-------
|
|
193
|
+
float
|
|
194
|
+
reorganization energy [eV].
|
|
195
|
+
"""
|
|
196
|
+
file_path = Path(self.gjf_file)
|
|
197
|
+
filename = file_path.stem.replace('_opt_n', '')
|
|
198
|
+
directory = file_path.parent
|
|
199
|
+
basename = f'{directory}/{filename}'
|
|
200
|
+
|
|
201
|
+
energy = []
|
|
202
|
+
|
|
203
|
+
# 中性分子の構造最適化とエネルギー計算
|
|
204
|
+
only_read_opt_n = only_read
|
|
205
|
+
if not only_read and 'opt_neutral' not in skip_specified_cal:
|
|
206
|
+
print('>', gau_com, self.gjf_file)
|
|
207
|
+
subprocess.run([gau_com, self.gjf_file])
|
|
208
|
+
|
|
209
|
+
skip_opt_neutral = True if 'opt_neutral' in skip_specified_cal else False
|
|
210
|
+
|
|
211
|
+
energy.append(self.extract_energy(self.gjf_file, only_read=only_read_opt_n, is_output_detail=is_output_detail, skip_cal=skip_opt_neutral))
|
|
212
|
+
|
|
213
|
+
# カチオンかアニオンのエネルギー計算
|
|
214
|
+
only_read_ion = only_read
|
|
215
|
+
previous_name, _ = os.path.splitext(self.gjf_file)
|
|
216
|
+
gjf = f'{basename}_{self.ion}.gjf'
|
|
217
|
+
if not only_read and 'ion' not in skip_specified_cal:
|
|
218
|
+
self._create_gjf(file_name=gjf, prevous_name=previous_name, ion=self.ion)
|
|
219
|
+
print('>', gau_com, gjf)
|
|
220
|
+
subprocess.run([gau_com, gjf])
|
|
221
|
+
|
|
222
|
+
skip_ion = True if 'ion' in skip_specified_cal else False
|
|
223
|
+
|
|
224
|
+
energy.append(self.extract_energy(gjf, only_read=only_read_ion, is_output_detail=is_output_detail, skip_cal=skip_ion))
|
|
225
|
+
|
|
226
|
+
# カチオンかアニオンの構造最適化とエネルギー計算
|
|
227
|
+
only_read_opt_ion = only_read
|
|
228
|
+
gjf = f'{basename}_opt_{self.ion}.gjf'
|
|
229
|
+
if not only_read and 'opt_ion' not in skip_specified_cal:
|
|
230
|
+
self._create_gjf(file_name=gjf, prevous_name=previous_name, ion=self.ion, is_opt=True)
|
|
231
|
+
print('>', gau_com, gjf)
|
|
232
|
+
subprocess.run([gau_com, gjf])
|
|
233
|
+
|
|
234
|
+
skip_opt_ion = True if 'opt_ion' in skip_specified_cal else False
|
|
235
|
+
|
|
236
|
+
energy.append(self.extract_energy(gjf, only_read=only_read_opt_ion, is_output_detail=is_output_detail, skip_cal=skip_opt_ion))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
# 中性分子のエネルギー計算
|
|
240
|
+
only_read_neutral = only_read
|
|
241
|
+
previous_name, _ = os.path.splitext(gjf)
|
|
242
|
+
ion = 'n'
|
|
243
|
+
gjf = f'{basename}_{ion}.gjf'
|
|
244
|
+
if not only_read and 'neutral' not in skip_specified_cal:
|
|
245
|
+
self._create_gjf(file_name=gjf, prevous_name=previous_name, ion=ion)
|
|
246
|
+
print('>', gau_com, gjf)
|
|
247
|
+
subprocess.run([gau_com, gjf])
|
|
248
|
+
|
|
249
|
+
skip_neutral = True if 'neutral' in skip_specified_cal else False
|
|
250
|
+
|
|
251
|
+
energy.append(self.extract_energy(gjf, only_read=only_read_neutral, is_output_detail=is_output_detail, skip_cal=skip_neutral))
|
|
252
|
+
|
|
253
|
+
return ((energy[3] - energy[2]) + (energy[1] - energy[0]))
|
|
254
|
+
|
|
255
|
+
def check_extension_log(self, gjf: str) -> None:
|
|
256
|
+
"""Check the extension of log file.
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
gjf : str
|
|
261
|
+
gjf file name.
|
|
262
|
+
"""
|
|
263
|
+
if os.path.exists(f'{os.path.splitext(gjf)[0]}.out'):
|
|
264
|
+
self._extension_log = '.out'
|
|
265
|
+
else :
|
|
266
|
+
self._extension_log = '.log'
|
|
267
|
+
|
|
268
|
+
def extract_energy(
|
|
269
|
+
self,
|
|
270
|
+
gjf: str,
|
|
271
|
+
only_read: bool = False,
|
|
272
|
+
is_output_detail: bool = False,
|
|
273
|
+
skip_cal: bool = False
|
|
274
|
+
) -> float:
|
|
275
|
+
"""Extract energy from log file.
|
|
276
|
+
|
|
277
|
+
Parameters
|
|
278
|
+
----------
|
|
279
|
+
gjf : str
|
|
280
|
+
gjf file name.
|
|
281
|
+
only_read : bool
|
|
282
|
+
if True, the calculation is only read, by default False.
|
|
283
|
+
is_output_detail : bool
|
|
284
|
+
if True, the calculation detail will be output, by default False.
|
|
285
|
+
|
|
286
|
+
Returns
|
|
287
|
+
-------
|
|
288
|
+
float
|
|
289
|
+
total energy.
|
|
290
|
+
"""
|
|
291
|
+
self.check_extension_log(gjf)
|
|
292
|
+
log_file = f'{os.path.splitext(gjf)[0]}{self._extension_log}'
|
|
293
|
+
|
|
294
|
+
with open(log_file) as f:
|
|
295
|
+
last_line = ''
|
|
296
|
+
while True:
|
|
297
|
+
line = f.readline()
|
|
298
|
+
if not line:
|
|
299
|
+
break
|
|
300
|
+
line = line.strip()
|
|
301
|
+
|
|
302
|
+
if line:
|
|
303
|
+
last_line = line
|
|
304
|
+
|
|
305
|
+
if line.startswith('SCF Done:'):
|
|
306
|
+
energy = float(line.split()[4]) * 27.2114
|
|
307
|
+
|
|
308
|
+
self.check_error_term(last_line)
|
|
309
|
+
|
|
310
|
+
if is_output_detail:
|
|
311
|
+
gjf = Path(gjf)
|
|
312
|
+
if not only_read and not skip_cal:
|
|
313
|
+
print(f'{gjf} calculation completed.')
|
|
314
|
+
elif skip_cal:
|
|
315
|
+
print(f'{gjf} calculation skipped.')
|
|
316
|
+
|
|
317
|
+
print(f'reading {gjf.parent}/{gjf.stem}{self._extension_log}')
|
|
318
|
+
print()
|
|
319
|
+
print('--------------')
|
|
320
|
+
print(' Total energy ')
|
|
321
|
+
print('--------------')
|
|
322
|
+
print(f'{energy:12.6f} eV')
|
|
323
|
+
print()
|
|
324
|
+
|
|
325
|
+
return energy
|
|
326
|
+
|
|
327
|
+
def _create_gjf(
|
|
328
|
+
self,
|
|
329
|
+
file_name: str,
|
|
330
|
+
prevous_name: str,
|
|
331
|
+
ion: Literal['c', 'a', 'n'],
|
|
332
|
+
is_opt: bool = False,
|
|
333
|
+
) -> None:
|
|
334
|
+
"""
|
|
335
|
+
Create gjf file.
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
file_name : str
|
|
340
|
+
file name.
|
|
341
|
+
prevous_name : str
|
|
342
|
+
previous file name.
|
|
343
|
+
ion : Literal['c', 'a', 'n']
|
|
344
|
+
ion type. 'c' is cation, 'a' is anion, 'n' is neutral molecule.
|
|
345
|
+
is_opt : bool
|
|
346
|
+
if True, the calculation is optimization, by default False.
|
|
347
|
+
"""
|
|
348
|
+
file_name, _ = os.path.splitext(file_name)
|
|
349
|
+
|
|
350
|
+
with open(f'{file_name}.gjf', 'w') as f:
|
|
351
|
+
for line in self._gjf_lines['%']:
|
|
352
|
+
if r'%oldchk' in line.lower():
|
|
353
|
+
continue
|
|
354
|
+
elif r'%chk' in line.lower():
|
|
355
|
+
continue
|
|
356
|
+
else:
|
|
357
|
+
f.write(line)
|
|
358
|
+
|
|
359
|
+
f.write(f'%oldchk={prevous_name}.chk\n')
|
|
360
|
+
if is_opt:
|
|
361
|
+
f.write(f'%chk={file_name}.chk\n')
|
|
362
|
+
|
|
363
|
+
for line in self._gjf_lines['#']:
|
|
364
|
+
if 'geom' in line.lower():
|
|
365
|
+
continue
|
|
366
|
+
elif 'opt' in line.lower():
|
|
367
|
+
continue
|
|
368
|
+
else:
|
|
369
|
+
f.write(line)
|
|
370
|
+
|
|
371
|
+
f.write('# Geom=Checkpoint\n')
|
|
372
|
+
if is_opt:
|
|
373
|
+
f.write('# Opt=Tight\n')
|
|
374
|
+
f.write('\n')
|
|
375
|
+
f.write('Defalut Title\n')
|
|
376
|
+
f.write('\n')
|
|
377
|
+
|
|
378
|
+
if ion == 'c':
|
|
379
|
+
f.write('1 2\n\n')
|
|
380
|
+
elif ion == 'a':
|
|
381
|
+
f.write('-1, 2\n\n')
|
|
382
|
+
else:
|
|
383
|
+
f.write('0 1\n\n')
|
|
384
|
+
|
|
385
|
+
def _input_gjf(self) -> None:
|
|
386
|
+
"""Input link 0 command and root options from gjf file."""
|
|
387
|
+
with open(self.gjf_file, 'r') as f:
|
|
388
|
+
for line in f:
|
|
389
|
+
if line.startswith('%'):
|
|
390
|
+
self._gjf_lines['%'].append(line)
|
|
391
|
+
elif line.startswith('#'):
|
|
392
|
+
self._gjf_lines['#'].append(line)
|
|
393
|
+
elif 'link' in line.lower():
|
|
394
|
+
raise ValueError("Please do not use Link.")
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class GausTermError(Exception):
|
|
398
|
+
"""Exception for Gaussian error termination."""
|
|
399
|
+
pass
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class OSCTypeError(Exception):
|
|
403
|
+
"""Exception for organic semiconductor type."""
|
|
404
|
+
pass
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
if __name__ == '__main__':
|
|
408
|
+
main()
|