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,1123 @@
|
|
|
1
|
+
"""Tcal"""
|
|
2
|
+
import argparse
|
|
3
|
+
import csv
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
import functools
|
|
6
|
+
import math
|
|
7
|
+
import os
|
|
8
|
+
import platform
|
|
9
|
+
import re
|
|
10
|
+
import subprocess
|
|
11
|
+
from time import time
|
|
12
|
+
import traceback
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
print = functools.partial(print, flush=True)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main():
|
|
21
|
+
"""This code is to execute tcal for command line."""
|
|
22
|
+
parser = argparse.ArgumentParser()
|
|
23
|
+
parser.add_argument('file', help='file name', type=str)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
'-a', '--apta', help='perform atomic pair transfer analysis', action='store_true'
|
|
26
|
+
)
|
|
27
|
+
parser.add_argument('-c', '--cube', help='generate cube files', action='store_true')
|
|
28
|
+
# parser.add_argument('-d', '--debug', help='enable detailed output', action='store_true')
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
'-g', '--g09', help='use Gaussian 09 (default is Gaussian 16)', action='store_true'
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
'-l', '--lumo', help='perform atomic pair transfer analysis of LUMO', action='store_true'
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
'-m', '--matrix', help='print MO coefficients, overlap matrix and Fock matrix', action='store_true'
|
|
37
|
+
)
|
|
38
|
+
parser.add_argument(
|
|
39
|
+
'-o', '--output', action='store_true',
|
|
40
|
+
help='output csv file on the result of apta and transfer integrals between diffrent orbitals etc.',
|
|
41
|
+
)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
'-r', '--read', help='read log files without executing Gaussian', action='store_true'
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument('-x', '--xyz', help='convert xyz to gjf', action='store_true')
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
'--napta', type=int, nargs=2, metavar=('N1', 'N2'),
|
|
48
|
+
help='perform atomic pair transfer analysis between different levels. ' \
|
|
49
|
+
'N1 is the number of level in the first monomer. N2 is the number of level in the second monomer.'
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
'--hetero', type=int, default=-1, metavar='N',
|
|
53
|
+
help='calculate the transfer integral of heterodimer. N is the number of atoms in the first monomer.'
|
|
54
|
+
)
|
|
55
|
+
parser.add_argument(
|
|
56
|
+
'--nlevel', type=int, metavar='N',
|
|
57
|
+
help='calculate transfer integrals between different levels. N is the number of levels from HOMO-LUMO. ' \
|
|
58
|
+
+ 'N=0 gives all levels.'
|
|
59
|
+
)
|
|
60
|
+
parser.add_argument(
|
|
61
|
+
'--skip', type=int, nargs='+', default=[0], metavar='N', choices=[1, 2, 3],
|
|
62
|
+
help='skip specified Gaussian calculation. If N is 1, skip 1st monomer calculation. ' \
|
|
63
|
+
+ 'If N is 2, skip 2nd monomer calculation. If N is 3, skip dimer calculation.'
|
|
64
|
+
)
|
|
65
|
+
args = parser.parse_args()
|
|
66
|
+
|
|
67
|
+
print('--------------------------------------')
|
|
68
|
+
print(' tcal 3.0 (2024/09/21) by Matsui Lab. ')
|
|
69
|
+
print('--------------------------------------')
|
|
70
|
+
print(f'\nInput File Name: {args.file}')
|
|
71
|
+
Tcal.print_timestamp()
|
|
72
|
+
before = time()
|
|
73
|
+
print()
|
|
74
|
+
|
|
75
|
+
tcal = Tcal(args.file, monomer1_atom_num=args.hetero)
|
|
76
|
+
|
|
77
|
+
# convert xyz to gjf
|
|
78
|
+
if args.xyz:
|
|
79
|
+
tcal.convert_xyz_to_gjf()
|
|
80
|
+
|
|
81
|
+
if not args.read:
|
|
82
|
+
tcal.create_monomer_file()
|
|
83
|
+
|
|
84
|
+
if args.g09:
|
|
85
|
+
gaussian_command = 'g09'
|
|
86
|
+
else:
|
|
87
|
+
gaussian_command = 'g16'
|
|
88
|
+
res = tcal.run_gaussian(gaussian_command, skip_monomer_num=args.skip)
|
|
89
|
+
|
|
90
|
+
if res:
|
|
91
|
+
print()
|
|
92
|
+
Tcal.print_timestamp()
|
|
93
|
+
after = time()
|
|
94
|
+
print(f'Elapsed Time: {(after - before) * 1000:.0f} ms')
|
|
95
|
+
exit()
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
tcal.check_extension_log()
|
|
99
|
+
tcal.read_monomer1(args.matrix)
|
|
100
|
+
tcal.read_monomer2(args.matrix)
|
|
101
|
+
tcal.read_dimer(args.matrix)
|
|
102
|
+
|
|
103
|
+
if args.cube:
|
|
104
|
+
tcal.create_cube_file()
|
|
105
|
+
|
|
106
|
+
tcal.print_transfer_integrals()
|
|
107
|
+
if args.nlevel is not None:
|
|
108
|
+
tcal.print_tranfer_integral_diff_levels(args.nlevel, output_ti_diff_levels=args.output)
|
|
109
|
+
|
|
110
|
+
if args.apta:
|
|
111
|
+
analyze_orbital = 'HOMO'
|
|
112
|
+
elif args.lumo:
|
|
113
|
+
analyze_orbital = 'LUMO'
|
|
114
|
+
|
|
115
|
+
if args.napta:
|
|
116
|
+
apta = tcal.custom_atomic_pair_transfer_analysis(
|
|
117
|
+
analyze_orb1=args.napta[0], analyze_orb2=args.napta[1], output_apta=args.output
|
|
118
|
+
)
|
|
119
|
+
pair_analysis = PairAnalysis(apta)
|
|
120
|
+
pair_analysis.print_largest_pairs()
|
|
121
|
+
pair_analysis.print_element_pairs()
|
|
122
|
+
elif args.apta or args.lumo:
|
|
123
|
+
apta = tcal.atomic_pair_transfer_analysis(analyze_orbital, output_apta=args.output)
|
|
124
|
+
pair_analysis = PairAnalysis(apta)
|
|
125
|
+
pair_analysis.print_largest_pairs()
|
|
126
|
+
pair_analysis.print_element_pairs()
|
|
127
|
+
print()
|
|
128
|
+
except:
|
|
129
|
+
print(traceback.format_exc().strip())
|
|
130
|
+
|
|
131
|
+
Tcal.print_timestamp()
|
|
132
|
+
after = time()
|
|
133
|
+
print(f'Elapsed Time: {(after - before) * 1000:.0f} ms')
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Tcal:
|
|
137
|
+
"""Calculate transfer integrals."""
|
|
138
|
+
EV = 4.35974417e-18 / 1.60217653e-19 * 1000.0
|
|
139
|
+
|
|
140
|
+
def __init__(self, file, monomer1_atom_num=-1):
|
|
141
|
+
"""Inits TcalClass.
|
|
142
|
+
|
|
143
|
+
Parameters
|
|
144
|
+
----------
|
|
145
|
+
file : str
|
|
146
|
+
A path of gjf file.
|
|
147
|
+
monomer1_atom_num: int
|
|
148
|
+
Number of atoms in the first monomer. default -1
|
|
149
|
+
If monomer1_atom_num is -1, it is half the number of atoms in the dimer.
|
|
150
|
+
"""
|
|
151
|
+
if platform.system() == 'Windows':
|
|
152
|
+
self._extension_log = '.out'
|
|
153
|
+
else:
|
|
154
|
+
self._extension_log = '.log'
|
|
155
|
+
|
|
156
|
+
self._base_path = os.path.splitext(file)[0]
|
|
157
|
+
self.monomer1_atom_num = monomer1_atom_num
|
|
158
|
+
|
|
159
|
+
self.n_basis1 = None
|
|
160
|
+
self.n_elect1 = None
|
|
161
|
+
self.n_bsuse1 = None
|
|
162
|
+
self.n_basis2 = None
|
|
163
|
+
self.n_elect2 = None
|
|
164
|
+
self.n_bsuse2 = None
|
|
165
|
+
self.n_basis_d = None
|
|
166
|
+
self.n_elect_d = None
|
|
167
|
+
self.mo1 = None
|
|
168
|
+
self.mo2 = None
|
|
169
|
+
self.overlap = None
|
|
170
|
+
self.fock = None
|
|
171
|
+
self.atom_index = None
|
|
172
|
+
self.atom_symbol = None
|
|
173
|
+
self.atom_orbital = None
|
|
174
|
+
|
|
175
|
+
self.n_atoms1 = None
|
|
176
|
+
self.n_atoms2 = None
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def cal_transfer_integrals(bra, overlap, fock, ket):
|
|
180
|
+
"""Calculate intermolecular transfer integrals.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
bra : numpy.array
|
|
185
|
+
MO coefficients of one molecule.
|
|
186
|
+
overlap : numpy.array
|
|
187
|
+
Overlap matrix of dimer.
|
|
188
|
+
fock : numpy.array
|
|
189
|
+
Fock matrix of dimer.
|
|
190
|
+
ket : numpy.array
|
|
191
|
+
MO coefficients of the other molecule.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
double
|
|
196
|
+
Intermolecular transfer integrals.
|
|
197
|
+
"""
|
|
198
|
+
s11 = bra @ overlap @ bra
|
|
199
|
+
s22 = ket @ overlap @ ket
|
|
200
|
+
s12 = bra @ overlap @ ket
|
|
201
|
+
f11 = bra @ fock @ bra
|
|
202
|
+
f22 = ket @ fock @ ket
|
|
203
|
+
f12 = bra @ fock @ ket
|
|
204
|
+
|
|
205
|
+
if abs(s11 - 1) > 1e-2:
|
|
206
|
+
print(f"WARNING! Self overlap is not unity: S11 = {s11}")
|
|
207
|
+
if abs(s22 - 1) > 1e-2:
|
|
208
|
+
print(f"WARNING! Self overlap is not unity: S22 = {s22}")
|
|
209
|
+
transfer = ((f12 - 0.5 * (f11 + f22) * s12) / (1 - s12 * s12)) * Tcal.EV
|
|
210
|
+
return transfer
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def check_normal_termination(reader):
|
|
214
|
+
"""Whether the calculation of gaussian was successful or not.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
reader : _io.TextIOWrapper
|
|
219
|
+
Return value of open function.
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
_io.TextIOWrapper
|
|
224
|
+
Return value of function.
|
|
225
|
+
|
|
226
|
+
Examples
|
|
227
|
+
--------
|
|
228
|
+
>>> with open('sample.log', 'r') as f:
|
|
229
|
+
... f = Tcal.check_normal_termination(f)
|
|
230
|
+
"""
|
|
231
|
+
while True:
|
|
232
|
+
line = reader.readline()
|
|
233
|
+
if not line:
|
|
234
|
+
return reader
|
|
235
|
+
if 'Normal termination' in line:
|
|
236
|
+
return reader
|
|
237
|
+
|
|
238
|
+
@staticmethod
|
|
239
|
+
def extract_coordinates(reader):
|
|
240
|
+
"""Extract coordinates from gjf file of dimer.
|
|
241
|
+
|
|
242
|
+
Parameters
|
|
243
|
+
----------
|
|
244
|
+
reader : _io.TextIOWrapper
|
|
245
|
+
Return value of open function.
|
|
246
|
+
|
|
247
|
+
Returns
|
|
248
|
+
-------
|
|
249
|
+
_io.TextIOWrapper
|
|
250
|
+
Return value of open function.
|
|
251
|
+
list
|
|
252
|
+
The list of coordinates.
|
|
253
|
+
|
|
254
|
+
Examples
|
|
255
|
+
--------
|
|
256
|
+
>>> import re
|
|
257
|
+
>>> with open(f'sample.gjf', 'r') as f:
|
|
258
|
+
... while True:
|
|
259
|
+
... line = f.readline()
|
|
260
|
+
... if not line:
|
|
261
|
+
... break
|
|
262
|
+
... if re.search('[-0-9]+ [0-3]', line):
|
|
263
|
+
... f, coordinates = Tcal.extract_coordinates(f)
|
|
264
|
+
"""
|
|
265
|
+
coordinates = []
|
|
266
|
+
while True:
|
|
267
|
+
line = reader.readline()
|
|
268
|
+
if not line.strip():
|
|
269
|
+
return reader, coordinates
|
|
270
|
+
coordinates.append(line)
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def extract_num(pattern, line, idx=0):
|
|
274
|
+
"""Extract integer in strings.
|
|
275
|
+
|
|
276
|
+
Parameters
|
|
277
|
+
----------
|
|
278
|
+
pattern : str
|
|
279
|
+
Strings using regular expression.
|
|
280
|
+
line : str
|
|
281
|
+
String of target.
|
|
282
|
+
|
|
283
|
+
Returns
|
|
284
|
+
-------
|
|
285
|
+
int or None
|
|
286
|
+
If there is integer, return it.
|
|
287
|
+
"""
|
|
288
|
+
res = re.search(pattern, line)
|
|
289
|
+
if res:
|
|
290
|
+
return int(res.group().split()[idx])
|
|
291
|
+
return None
|
|
292
|
+
|
|
293
|
+
@staticmethod
|
|
294
|
+
def output_csv(file_name, array):
|
|
295
|
+
"""Output csv file of array.
|
|
296
|
+
|
|
297
|
+
Parameters
|
|
298
|
+
----------
|
|
299
|
+
file_name : str
|
|
300
|
+
File name including extension.
|
|
301
|
+
array : array_like
|
|
302
|
+
Array to create csv file.
|
|
303
|
+
"""
|
|
304
|
+
with open(file_name, 'w', encoding='UTF-8', newline='') as f:
|
|
305
|
+
writer = csv.writer(f)
|
|
306
|
+
writer.writerows(array)
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
def print_matrix(matrix):
|
|
310
|
+
"""Print matrix.
|
|
311
|
+
|
|
312
|
+
Parameters
|
|
313
|
+
----------
|
|
314
|
+
matrix : array_like
|
|
315
|
+
"""
|
|
316
|
+
for i, row in enumerate(matrix):
|
|
317
|
+
for j, cell in enumerate(row[:-1]):
|
|
318
|
+
if i == 0 or j == 0:
|
|
319
|
+
print(f'{cell:^9}', end='\t')
|
|
320
|
+
else:
|
|
321
|
+
print(f'{cell:>9}', end='\t')
|
|
322
|
+
if i == 0:
|
|
323
|
+
print(f'{row[-1]:^9}')
|
|
324
|
+
else:
|
|
325
|
+
print(f'{row[-1]:>9}')
|
|
326
|
+
|
|
327
|
+
@staticmethod
|
|
328
|
+
def print_timestamp():
|
|
329
|
+
"""Print timestamp."""
|
|
330
|
+
month = {
|
|
331
|
+
1: 'Jan', 2: 'Feb', 3: 'Mar', 4: 'Apr', 5: 'May', 6: 'Jun',
|
|
332
|
+
7: 'Jul', 8: 'Aug', 9: 'Sep', 10: 'Oct', 11: 'Nov', 12: 'Dec',
|
|
333
|
+
}
|
|
334
|
+
dt_now = datetime.now()
|
|
335
|
+
print(f"Timestamp: {dt_now.strftime('%a')} {month[dt_now.month]} {dt_now.strftime('%d %H:%M:%S %Y')}")
|
|
336
|
+
|
|
337
|
+
@staticmethod
|
|
338
|
+
def read_matrix(reader, n_basis, n_bsuse):
|
|
339
|
+
"""Read matrix.
|
|
340
|
+
|
|
341
|
+
Parameters
|
|
342
|
+
----------
|
|
343
|
+
reader : _io.TextIOWrapper
|
|
344
|
+
Return value of open function.
|
|
345
|
+
n_basis : int
|
|
346
|
+
The number of row.
|
|
347
|
+
n_bsuse : int
|
|
348
|
+
The number of column.
|
|
349
|
+
|
|
350
|
+
Returns
|
|
351
|
+
-------
|
|
352
|
+
numpy.array
|
|
353
|
+
Read matrix like MO coefficients.
|
|
354
|
+
"""
|
|
355
|
+
mat = np.zeros((n_basis, n_bsuse), dtype=np.float64)
|
|
356
|
+
for i in range(math.ceil(n_bsuse/5)):
|
|
357
|
+
if 'Alpha density matrix' in reader.readline():
|
|
358
|
+
break
|
|
359
|
+
for j in range(n_basis):
|
|
360
|
+
for k, val in enumerate(reader.readline().split()[1:]):
|
|
361
|
+
mat[j][i*5+k] = float(val.strip().replace('D', 'E'))
|
|
362
|
+
|
|
363
|
+
return mat
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def read_symmetric_matrix(reader, n_basis):
|
|
367
|
+
"""Read symmetric matrix.
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
reader : _io.TextIOWrapper
|
|
372
|
+
Return value of open function.
|
|
373
|
+
n_basis : int
|
|
374
|
+
The number of column or row.
|
|
375
|
+
|
|
376
|
+
Returns
|
|
377
|
+
-------
|
|
378
|
+
numpy.array
|
|
379
|
+
Read symmetrix matrix like overlap or fock matrix.
|
|
380
|
+
"""
|
|
381
|
+
mat = np.zeros((n_basis, n_basis), dtype=np.float64)
|
|
382
|
+
for i in range(math.ceil(n_basis/5)):
|
|
383
|
+
reader.readline()
|
|
384
|
+
for j in range(i*5, n_basis):
|
|
385
|
+
for k, val in enumerate(reader.readline().split()[1:]):
|
|
386
|
+
val = float(val.strip().replace('D', 'E'))
|
|
387
|
+
mat[j][i*5+k] = val
|
|
388
|
+
mat[i*5+k][j] = val
|
|
389
|
+
|
|
390
|
+
return mat
|
|
391
|
+
|
|
392
|
+
def atomic_pair_transfer_analysis(self, analyze_orbital='HOMO', output_apta=False):
|
|
393
|
+
"""Calculate atomic pair transfer integrals.
|
|
394
|
+
|
|
395
|
+
Parameters
|
|
396
|
+
----------
|
|
397
|
+
analyze_orbital : str, optional
|
|
398
|
+
Analyze orbital., default 'HOMO'
|
|
399
|
+
output_apta : bool, optional
|
|
400
|
+
If it is True, output csv file of atomic pair transfer integrals., default False
|
|
401
|
+
"""
|
|
402
|
+
if analyze_orbital.upper() == 'LUMO':
|
|
403
|
+
orb1 = self.mo1[self.n_elect1]
|
|
404
|
+
orb2 = self.mo2[self.n_elect2]
|
|
405
|
+
else:
|
|
406
|
+
orb1 = self.mo1[self.n_elect1 - 1]
|
|
407
|
+
orb2 = self.mo2[self.n_elect2 - 1]
|
|
408
|
+
|
|
409
|
+
f11 = orb1 @ self.fock @ orb1
|
|
410
|
+
f22 = orb2 @ self.fock @ orb2
|
|
411
|
+
s12 = orb1 @ self.overlap @ orb2
|
|
412
|
+
|
|
413
|
+
sum_f = f11 + f22
|
|
414
|
+
pow_s12 = 1 - s12*s12
|
|
415
|
+
atom_num = len(set(self.atom_index))
|
|
416
|
+
a_transfer = np.zeros((atom_num, atom_num))
|
|
417
|
+
for i in range(self.n_basis_d):
|
|
418
|
+
for j in range(self.n_basis_d):
|
|
419
|
+
orb_transfer = orb1[i] * orb2[j] * (self.fock[i][j] - 0.5 * sum_f * self.overlap[i][j]) / pow_s12
|
|
420
|
+
a_transfer[self.atom_index[i]][self.atom_index[j]] += orb_transfer
|
|
421
|
+
|
|
422
|
+
apta = self.print_apta(a_transfer)
|
|
423
|
+
|
|
424
|
+
if output_apta:
|
|
425
|
+
self.output_csv(f'{self._base_path}_apta_{analyze_orbital}.csv', apta)
|
|
426
|
+
|
|
427
|
+
return apta
|
|
428
|
+
|
|
429
|
+
def check_extension_log(self):
|
|
430
|
+
"""Check the extension of log file."""
|
|
431
|
+
if os.path.exists(f'{self._base_path}.out'):
|
|
432
|
+
self._extension_log = '.out'
|
|
433
|
+
else :
|
|
434
|
+
self._extension_log = '.log'
|
|
435
|
+
|
|
436
|
+
def convert_xyz_to_gjf(self, function='B3LYP/6-31G(d,p)', nprocshared=4, mem=16, unit='GB'):
|
|
437
|
+
"""Convert xyz file to gjf file.
|
|
438
|
+
|
|
439
|
+
Parameters
|
|
440
|
+
----------
|
|
441
|
+
function : str, optional
|
|
442
|
+
_description_, default 'b3lyp/6-31g(d,p)'
|
|
443
|
+
nprocshared : int, optional
|
|
444
|
+
The number of nprocshared., default 4
|
|
445
|
+
mem : int, optional
|
|
446
|
+
The number of memory., default 16
|
|
447
|
+
unit : str, optional
|
|
448
|
+
The unit of memory., default 'GB'
|
|
449
|
+
"""
|
|
450
|
+
coordinates = []
|
|
451
|
+
with open(f'{self._base_path}.xyz', 'r') as f:
|
|
452
|
+
while True:
|
|
453
|
+
line = f.readline()
|
|
454
|
+
if not line:
|
|
455
|
+
break
|
|
456
|
+
line_list = line.strip().split()
|
|
457
|
+
if len(line_list) == 4:
|
|
458
|
+
symbol, x, y, z = line_list
|
|
459
|
+
try:
|
|
460
|
+
x, y, z = map(float, [x, y, z])
|
|
461
|
+
x = f'{x:.8f}'.rjust(14, ' ')
|
|
462
|
+
y = f'{y:.8f}'.rjust(14, ' ')
|
|
463
|
+
z = f'{z:.8f}'.rjust(14, ' ')
|
|
464
|
+
coordinates.append(f' {symbol} {x} {y} {z}\n')
|
|
465
|
+
except ValueError:
|
|
466
|
+
continue
|
|
467
|
+
|
|
468
|
+
with open(f'{self._base_path}.gjf', 'w') as f:
|
|
469
|
+
f.write(f'%Chk={self._base_path}.chk')
|
|
470
|
+
f.write('\n')
|
|
471
|
+
f.write(f'%NProcShared={nprocshared}')
|
|
472
|
+
f.write('\n')
|
|
473
|
+
f.write(f'%Mem={mem}{unit}\n')
|
|
474
|
+
f.write(f'# {function}\n')
|
|
475
|
+
f.write('# Symmetry=None\n')
|
|
476
|
+
f.write('\n')
|
|
477
|
+
f.write(f'{self._base_path}.gjf created by tcal\n')
|
|
478
|
+
f.write('\n')
|
|
479
|
+
f.write('0 1\n')
|
|
480
|
+
|
|
481
|
+
for coordinate in coordinates:
|
|
482
|
+
f.write(coordinate)
|
|
483
|
+
|
|
484
|
+
f.write('\n')
|
|
485
|
+
f.write('--Link1--\n')
|
|
486
|
+
f.write(f'%Chk={self._base_path}.chk')
|
|
487
|
+
f.write('\n')
|
|
488
|
+
f.write(f'%NProcShared={nprocshared}')
|
|
489
|
+
f.write('\n')
|
|
490
|
+
f.write(f'%Mem={mem}{unit}\n')
|
|
491
|
+
f.write(f'# {function}\n')
|
|
492
|
+
f.write('# Symmetry=None\n')
|
|
493
|
+
f.write('# Geom=AllCheck\n')
|
|
494
|
+
f.write('# Guess=Read\n')
|
|
495
|
+
f.write('# Pop=Full\n')
|
|
496
|
+
f.write('# IOp(3/33=4,5/33=3)\n')
|
|
497
|
+
f.write('\n')
|
|
498
|
+
|
|
499
|
+
def create_cube_file(self):
|
|
500
|
+
"""Create cube file."""
|
|
501
|
+
self._execute(['formchk', f'{self._base_path}.chk', f'{self._base_path}.fchk'])
|
|
502
|
+
self._create_dummy_cube_file()
|
|
503
|
+
self._execute(['formchk', f'{self._base_path}_m1.chk', f'{self._base_path}_m1.fchk'])
|
|
504
|
+
self._execute(['cubegen', '0', f'mo={self.n_elect1-1}', f'{self._base_path}_m1.fchk', f'{self._base_path}_m1_NHOMO.cube', '-1', 'h', f'{self._base_path}_dummy.cube'])
|
|
505
|
+
self._execute(['cubegen', '0', f'mo={self.n_elect1}', f'{self._base_path}_m1.fchk', f'{self._base_path}_m1_HOMO.cube', '-1', 'h', f'{self._base_path}_dummy.cube'])
|
|
506
|
+
self._execute(['cubegen', '0', f'mo={self.n_elect1+1}', f'{self._base_path}_m1.fchk', f'{self._base_path}_m1_LUMO.cube', '-1', 'h', f'{self._base_path}_dummy.cube'])
|
|
507
|
+
self._execute(['cubegen', '0', f'mo={self.n_elect1+2}', f'{self._base_path}_m1.fchk', f'{self._base_path}_m1_NLUMO.cube', '-1', 'h', f'{self._base_path}_dummy.cube'])
|
|
508
|
+
print('cube file of the 1st monomer created')
|
|
509
|
+
print(f' {self._base_path}_m1_NHOMO.cube')
|
|
510
|
+
print(f' {self._base_path}_m1_HOMO.cube')
|
|
511
|
+
print(f' {self._base_path}_m1_LUMO.cube')
|
|
512
|
+
print(f' {self._base_path}_m1_NLUMO.cube')
|
|
513
|
+
|
|
514
|
+
self._execute(['formchk', f'{self._base_path}_m2.chk', f'{self._base_path}_m2.fchk'])
|
|
515
|
+
self._execute(['cubegen', '0', f'mo={self.n_elect2-1}', f'{self._base_path}_m2.fchk', f'{self._base_path}_m2_NHOMO.cube', '-1', 'h', f'{self._base_path}_dummy.cube'])
|
|
516
|
+
self._execute(['cubegen', '0', f'mo={self.n_elect2}', f'{self._base_path}_m2.fchk', f'{self._base_path}_m2_HOMO.cube', '-1', 'h', f'{self._base_path}_dummy.cube'])
|
|
517
|
+
self._execute(['cubegen', '0', f'mo={self.n_elect2+1}', f'{self._base_path}_m2.fchk', f'{self._base_path}_m2_LUMO.cube', '-1', 'h', f'{self._base_path}_dummy.cube'])
|
|
518
|
+
self._execute(['cubegen', '0', f'mo={self.n_elect2+2}', f'{self._base_path}_m2.fchk', f'{self._base_path}_m2_NLUMO.cube', '-1', 'h', f'{self._base_path}_dummy.cube'])
|
|
519
|
+
print('cube file of the 2nd monomer created')
|
|
520
|
+
print(f' {self._base_path}_m2_NHOMO.cube')
|
|
521
|
+
print(f' {self._base_path}_m2_HOMO.cube')
|
|
522
|
+
print(f' {self._base_path}_m2_LUMO.cube')
|
|
523
|
+
print(f' {self._base_path}_m2_NLUMO.cube')
|
|
524
|
+
|
|
525
|
+
def create_monomer_file(self):
|
|
526
|
+
"""Create gjf files of monomer from gjf file of dimer."""
|
|
527
|
+
link0 = []
|
|
528
|
+
# List for storing characters between coordinates and link1 when gem is used for the basis function.
|
|
529
|
+
basis_gem = ['\n']
|
|
530
|
+
link1 = []
|
|
531
|
+
is_link0 = True
|
|
532
|
+
is_link1 = False
|
|
533
|
+
with open(f'{self._base_path}.gjf', 'r', encoding='utf-8') as f:
|
|
534
|
+
while True:
|
|
535
|
+
line = f.readline()
|
|
536
|
+
if not line:
|
|
537
|
+
break
|
|
538
|
+
|
|
539
|
+
if re.search('[-0-9]+ [0-3]', line):
|
|
540
|
+
link0.append('0 1\n')
|
|
541
|
+
f, coordinates = self.extract_coordinates(f)
|
|
542
|
+
is_link0 = False
|
|
543
|
+
elif r'%chk=' in line.lower():
|
|
544
|
+
continue
|
|
545
|
+
elif '--link1--' == line.strip().lower():
|
|
546
|
+
is_link1 = True
|
|
547
|
+
link1.append(line)
|
|
548
|
+
elif is_link0:
|
|
549
|
+
link0.append(line)
|
|
550
|
+
elif is_link1:
|
|
551
|
+
link1.append(line)
|
|
552
|
+
else:
|
|
553
|
+
basis_gem.append(line)
|
|
554
|
+
|
|
555
|
+
if self.monomer1_atom_num == -1:
|
|
556
|
+
mono_coordinates = coordinates[:len(coordinates)//2]
|
|
557
|
+
else:
|
|
558
|
+
mono_coordinates = coordinates[:self.monomer1_atom_num]
|
|
559
|
+
|
|
560
|
+
for i in (1, 2):
|
|
561
|
+
with open(f'{self._base_path}_m{i}.gjf', 'w', encoding='utf-8') as f:
|
|
562
|
+
f.write(f'%Chk={self._base_path}_m{i}.chk\n')
|
|
563
|
+
f.writelines(link0)
|
|
564
|
+
|
|
565
|
+
if i == 2:
|
|
566
|
+
if self.monomer1_atom_num == -1:
|
|
567
|
+
mono_coordinates = coordinates[len(coordinates)//2:]
|
|
568
|
+
else:
|
|
569
|
+
mono_coordinates = coordinates[self.monomer1_atom_num:]
|
|
570
|
+
|
|
571
|
+
f.writelines(mono_coordinates)
|
|
572
|
+
f.writelines(basis_gem)
|
|
573
|
+
|
|
574
|
+
f.write(f'{link1[0]}')
|
|
575
|
+
f.write(f'%Chk={self._base_path}_m{i}.chk\n')
|
|
576
|
+
f.writelines(link1[1:])
|
|
577
|
+
|
|
578
|
+
print('monomer gjf file created')
|
|
579
|
+
print(f' {self._base_path}_m1.gjf')
|
|
580
|
+
print(f' {self._base_path}_m2.gjf')
|
|
581
|
+
|
|
582
|
+
def custom_atomic_pair_transfer_analysis(self, analyze_orb1, analyze_orb2, output_apta=False):
|
|
583
|
+
"""Calculate atomic pair transfer integrals.
|
|
584
|
+
|
|
585
|
+
Parameters
|
|
586
|
+
----------
|
|
587
|
+
analyze_orb1 : int, optional
|
|
588
|
+
Analyze orbital., default -1
|
|
589
|
+
analyze_orb2 : int, optional
|
|
590
|
+
Analyze orbital., default -1
|
|
591
|
+
output_apta : bool, optional
|
|
592
|
+
If it is True, output csv file of atomic pair transfer integrals., default False
|
|
593
|
+
"""
|
|
594
|
+
orb1 = self.mo1[analyze_orb1-1]
|
|
595
|
+
orb2 = self.mo2[analyze_orb2-1]
|
|
596
|
+
|
|
597
|
+
f11 = orb1 @ self.fock @ orb1
|
|
598
|
+
f22 = orb2 @ self.fock @ orb2
|
|
599
|
+
s12 = orb1 @ self.overlap @ orb2
|
|
600
|
+
|
|
601
|
+
sum_f = f11 + f22
|
|
602
|
+
pow_s12 = 1 - s12*s12
|
|
603
|
+
atom_num = len(set(self.atom_index))
|
|
604
|
+
a_transfer = np.zeros((atom_num, atom_num))
|
|
605
|
+
for i in range(self.n_basis_d):
|
|
606
|
+
for j in range(self.n_basis_d):
|
|
607
|
+
orb_transfer = orb1[i] * orb2[j] * (self.fock[i][j] - 0.5 * sum_f * self.overlap[i][j]) / pow_s12
|
|
608
|
+
a_transfer[self.atom_index[i]][self.atom_index[j]] += orb_transfer
|
|
609
|
+
|
|
610
|
+
apta = self.print_apta(
|
|
611
|
+
a_transfer,
|
|
612
|
+
message=f'Atomic Pair Transfer Analysis (monomer 1 is {analyze_orb1}-th orbital, monomer 2 is {analyze_orb2}-th orbital)'
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
if output_apta:
|
|
616
|
+
self.output_csv(f'{self._base_path}_apta_{analyze_orb1}_{analyze_orb2}.csv', apta)
|
|
617
|
+
|
|
618
|
+
return apta
|
|
619
|
+
|
|
620
|
+
def print_apta(self, a_transfer, message='Atomic Pair Transfer Analysis'):
|
|
621
|
+
"""Create list of apta and print it.
|
|
622
|
+
|
|
623
|
+
Parameters
|
|
624
|
+
----------
|
|
625
|
+
a_transfer : numpy.array
|
|
626
|
+
Result of atomic pair transfer analysis.
|
|
627
|
+
message : str, optional
|
|
628
|
+
Message to print., default 'Atomic Pair Transfer Analysis'
|
|
629
|
+
|
|
630
|
+
Returns
|
|
631
|
+
-------
|
|
632
|
+
numpy.array
|
|
633
|
+
The array of atomic pair transfer analysis.
|
|
634
|
+
"""
|
|
635
|
+
n_atoms = self.atom_index[-1] + 1
|
|
636
|
+
|
|
637
|
+
labels = [''] * n_atoms
|
|
638
|
+
for i in range(self.n_basis_d):
|
|
639
|
+
labels[self.atom_index[i]] = self.atom_symbol[i]
|
|
640
|
+
|
|
641
|
+
col_sum = np.sum(a_transfer, axis=1)
|
|
642
|
+
row_sum = np.sum(a_transfer, axis=0)
|
|
643
|
+
total_sum = np.sum(a_transfer)
|
|
644
|
+
|
|
645
|
+
print()
|
|
646
|
+
print('-' * (len(message)+2))
|
|
647
|
+
print(f' {message} ')
|
|
648
|
+
print('-' * (len(message)+2))
|
|
649
|
+
apta = []
|
|
650
|
+
tmp_list = ['atom']
|
|
651
|
+
for i in range(self.n_atoms1, n_atoms):
|
|
652
|
+
tmp_list.append(f'{i+1}{labels[i]}')
|
|
653
|
+
tmp_list.append('sum')
|
|
654
|
+
apta.append(tmp_list)
|
|
655
|
+
|
|
656
|
+
for i in range(self.n_atoms1):
|
|
657
|
+
tmp_list = []
|
|
658
|
+
tmp_list.append(f'{i+1}{labels[i]}')
|
|
659
|
+
for j in range(self.n_atoms1, n_atoms):
|
|
660
|
+
tmp_list.append(f'{a_transfer[i][j] * Tcal.EV:.3f}')
|
|
661
|
+
tmp_list.append(f'{col_sum[i] * Tcal.EV:.3f}')
|
|
662
|
+
apta.append(tmp_list)
|
|
663
|
+
|
|
664
|
+
tmp_list = ['sum']
|
|
665
|
+
for j in range(self.n_atoms1, n_atoms):
|
|
666
|
+
tmp_list.append(f'{row_sum[j] * Tcal.EV:.3f}')
|
|
667
|
+
tmp_list.append(f'{total_sum * Tcal.EV:.3f}')
|
|
668
|
+
apta.append(tmp_list)
|
|
669
|
+
|
|
670
|
+
self.print_matrix(apta)
|
|
671
|
+
|
|
672
|
+
return apta
|
|
673
|
+
|
|
674
|
+
def print_transfer_integrals(self):
|
|
675
|
+
"""Print transfer integrals of NLUMO, LUMO, HOMO and NHOMO."""
|
|
676
|
+
print()
|
|
677
|
+
print("--------------------")
|
|
678
|
+
print(" Transfer Integrals ")
|
|
679
|
+
print("--------------------")
|
|
680
|
+
transfer = self.cal_transfer_integrals(
|
|
681
|
+
self.mo1[self.n_elect1 + 1], self.overlap, self.fock, self.mo2[self.n_elect2 + 1]
|
|
682
|
+
)
|
|
683
|
+
print(f'NLUMO\t{transfer:>9.3f}\tmeV')
|
|
684
|
+
|
|
685
|
+
transfer = self.cal_transfer_integrals(
|
|
686
|
+
self.mo1[self.n_elect1], self.overlap, self.fock, self.mo2[self.n_elect2]
|
|
687
|
+
)
|
|
688
|
+
print(f'LUMO \t{transfer:>9.3f}\tmeV')
|
|
689
|
+
|
|
690
|
+
transfer = self.cal_transfer_integrals(
|
|
691
|
+
self.mo1[self.n_elect1 - 1], self.overlap, self.fock, self.mo2[self.n_elect2 - 1]
|
|
692
|
+
)
|
|
693
|
+
print(f'HOMO \t{transfer:>9.3f}\tmeV')
|
|
694
|
+
|
|
695
|
+
transfer = self.cal_transfer_integrals(
|
|
696
|
+
self.mo1[self.n_elect1 - 2], self.overlap, self.fock, self.mo2[self.n_elect2 - 2]
|
|
697
|
+
)
|
|
698
|
+
print(f'NHOMO\t{transfer:>9.3f}\tmeV')
|
|
699
|
+
|
|
700
|
+
def print_tranfer_integral_diff_levels(self, nlevel, output_ti_diff_levels=False):
|
|
701
|
+
print()
|
|
702
|
+
print('----------------------------------------------')
|
|
703
|
+
print(' Tranfer Integrals between Different Orbitals ')
|
|
704
|
+
print('----------------------------------------------')
|
|
705
|
+
|
|
706
|
+
if nlevel == 0:
|
|
707
|
+
start1 = 0
|
|
708
|
+
start2 = 0
|
|
709
|
+
end1 = len(self.mo1)
|
|
710
|
+
end2 = len(self.mo2)
|
|
711
|
+
else:
|
|
712
|
+
start1 = max(0, self.n_elect1 - nlevel)
|
|
713
|
+
start2 = max(0, self.n_elect2 - nlevel)
|
|
714
|
+
end1 = min(len(self.mo1), self.n_elect1 + nlevel)
|
|
715
|
+
end2 = min(len(self.mo2), self.n_elect2 + nlevel)
|
|
716
|
+
|
|
717
|
+
print(f'HOMO of monomer 1 is {self.n_elect1}-th orbital')
|
|
718
|
+
print(f'HOMO of monomer 2 is {self.n_elect2}-th orbital')
|
|
719
|
+
print(f'print all combinations between ({start1+1}, {end1}) and ({start2+1}, {end2}).')
|
|
720
|
+
print()
|
|
721
|
+
|
|
722
|
+
ti_diff_levels = []
|
|
723
|
+
tmp_list = ['mono1/mono2']
|
|
724
|
+
for i in range(start2, end2):
|
|
725
|
+
if i+1 == self.n_elect2:
|
|
726
|
+
tmp_list.append(f'HOMO({i+1})')
|
|
727
|
+
elif i+1 == self.n_elect2+1:
|
|
728
|
+
tmp_list.append(f'LUMO({i+1})')
|
|
729
|
+
else:
|
|
730
|
+
tmp_list.append(f'{i+1}')
|
|
731
|
+
ti_diff_levels.append(tmp_list)
|
|
732
|
+
|
|
733
|
+
for i in range(start1, end1):
|
|
734
|
+
if i+1 == self.n_elect1:
|
|
735
|
+
tmp_list = [f'HOMO({i+1})']
|
|
736
|
+
elif i+1 == self.n_elect1+1:
|
|
737
|
+
tmp_list = [f'LUMO({i+1})']
|
|
738
|
+
else:
|
|
739
|
+
tmp_list = [f'{i+1}']
|
|
740
|
+
for j in range(start2, end2):
|
|
741
|
+
transfer = self.cal_transfer_integrals(
|
|
742
|
+
self.mo1[i], self.overlap, self.fock, self.mo2[j]
|
|
743
|
+
)
|
|
744
|
+
tmp_list.append(f'{transfer:.3f}')
|
|
745
|
+
ti_diff_levels.append(tmp_list)
|
|
746
|
+
|
|
747
|
+
self.print_matrix(ti_diff_levels)
|
|
748
|
+
|
|
749
|
+
if output_ti_diff_levels:
|
|
750
|
+
self.output_csv(f'{self._base_path}_ti_diff_levels.csv', ti_diff_levels)
|
|
751
|
+
|
|
752
|
+
def read_monomer1(self, is_matrix=False, output_matrix=False):
|
|
753
|
+
"""Extract MO coefficients from log file of monomer.
|
|
754
|
+
|
|
755
|
+
Parameters
|
|
756
|
+
----------
|
|
757
|
+
is_matrix : bool, optional
|
|
758
|
+
If it is True, print MO coefficients., default False
|
|
759
|
+
output_matrix : bool, optional
|
|
760
|
+
If it is True, Output MO coefficients., default False
|
|
761
|
+
"""
|
|
762
|
+
print(f'reading {self._base_path}_m1{self._extension_log}')
|
|
763
|
+
with open(f'{self._base_path}_m1{self._extension_log}', 'r', encoding='utf-8') as f:
|
|
764
|
+
f = self.check_normal_termination(f)
|
|
765
|
+
while True:
|
|
766
|
+
line = f.readline()
|
|
767
|
+
if not line:
|
|
768
|
+
break
|
|
769
|
+
|
|
770
|
+
if self.n_basis1 is None:
|
|
771
|
+
self.n_basis1 = self.extract_num('[0-9]+ basis functions', line)
|
|
772
|
+
|
|
773
|
+
if self.n_bsuse1 is None:
|
|
774
|
+
self.n_bsuse1 = self.extract_num(r'NBsUse=\s*[0-9]+', line, idx=-1)
|
|
775
|
+
|
|
776
|
+
if self.n_elect1 is None:
|
|
777
|
+
self.n_elect1 = self.extract_num('[0-9]+ beta electrons', line)
|
|
778
|
+
|
|
779
|
+
if self.n_atoms1 is None:
|
|
780
|
+
self.n_atoms1 = self.extract_num(r'NAtoms=\s*[0-9]+', line, idx=-1)
|
|
781
|
+
|
|
782
|
+
if 'Alpha MO coefficients at cycle' in line:
|
|
783
|
+
self.mo1 = self.read_matrix(f, self.n_basis1, self.n_bsuse1)
|
|
784
|
+
break
|
|
785
|
+
|
|
786
|
+
if is_matrix:
|
|
787
|
+
print(' *** Alpha MO coefficients *** ')
|
|
788
|
+
self.print_matrix(self.mo1)
|
|
789
|
+
|
|
790
|
+
if output_matrix:
|
|
791
|
+
self.output_csv(f'{self._base_path}_mo1.csv', self.mo1)
|
|
792
|
+
|
|
793
|
+
def read_monomer2(self, is_matrix=False, output_matrix=False):
|
|
794
|
+
"""Extract MO coefficients from log file of monomer.
|
|
795
|
+
|
|
796
|
+
Parameters
|
|
797
|
+
----------
|
|
798
|
+
is_matrix : bool, optional
|
|
799
|
+
If it is True, print MO coefficients., default False
|
|
800
|
+
output_matrix : bool, optional
|
|
801
|
+
If it is True, Output MO coefficients., default False
|
|
802
|
+
"""
|
|
803
|
+
print(f'reading {self._base_path}_m2{self._extension_log}')
|
|
804
|
+
with open(f'{self._base_path}_m2{self._extension_log}', 'r', encoding='utf-8') as f:
|
|
805
|
+
f = self.check_normal_termination(f)
|
|
806
|
+
while True:
|
|
807
|
+
line = f.readline()
|
|
808
|
+
if not line:
|
|
809
|
+
break
|
|
810
|
+
|
|
811
|
+
if self.n_basis2 is None:
|
|
812
|
+
self.n_basis2 = self.extract_num('[0-9]+ basis functions', line)
|
|
813
|
+
|
|
814
|
+
if self.n_bsuse2 is None:
|
|
815
|
+
self.n_bsuse2 = self.extract_num(r'NBsUse=\s*[0-9]+', line, idx=-1)
|
|
816
|
+
|
|
817
|
+
if self.n_elect2 is None:
|
|
818
|
+
self.n_elect2 = self.extract_num('[0-9]+ alpha electrons', line)
|
|
819
|
+
|
|
820
|
+
if self.n_atoms2 is None:
|
|
821
|
+
self.n_atoms2 = self.extract_num(r'NAtoms=\s*[0-9]+', line, idx=-1)
|
|
822
|
+
|
|
823
|
+
if 'Alpha MO coefficients at cycle' in line:
|
|
824
|
+
self.mo2 = self.read_matrix(f, self.n_basis2, self.n_bsuse2)
|
|
825
|
+
break
|
|
826
|
+
|
|
827
|
+
if is_matrix:
|
|
828
|
+
print(' *** Alpha MO coefficients *** ')
|
|
829
|
+
self.print_matrix(self.mo2)
|
|
830
|
+
|
|
831
|
+
if output_matrix:
|
|
832
|
+
self.output_csv(f'{self._base_path}_mo2.csv', self.mo2)
|
|
833
|
+
|
|
834
|
+
def read_dimer(self, is_matrix=False, output_matrix=False):
|
|
835
|
+
"""Extract overlap and fock matrix from log file of dimer.
|
|
836
|
+
|
|
837
|
+
Parameters
|
|
838
|
+
----------
|
|
839
|
+
is_matrix : bool, optional
|
|
840
|
+
If it is True, print overlap and fock matrix., default False
|
|
841
|
+
output_matrix : bool, optional
|
|
842
|
+
If it is True, Output overlap and fock matrix., default False
|
|
843
|
+
"""
|
|
844
|
+
print(f'reading {self._base_path}{self._extension_log}')
|
|
845
|
+
with open(f'{self._base_path}{self._extension_log}', 'r', encoding='utf-8') as f:
|
|
846
|
+
f = self.check_normal_termination(f)
|
|
847
|
+
while True:
|
|
848
|
+
line = f.readline()
|
|
849
|
+
if not line:
|
|
850
|
+
break
|
|
851
|
+
|
|
852
|
+
if self.n_basis_d is None:
|
|
853
|
+
self.n_basis_d = self.extract_num('[0-9]+ basis functions', line)
|
|
854
|
+
|
|
855
|
+
if self.n_elect_d is None:
|
|
856
|
+
self.n_elect_d = self.extract_num('[0-9]+ beta electrons', line)
|
|
857
|
+
|
|
858
|
+
if '*** Overlap ***' in line:
|
|
859
|
+
self.overlap = self.read_symmetric_matrix(f, self.n_basis_d)
|
|
860
|
+
|
|
861
|
+
if 'Fock matrix (alpha):' in line:
|
|
862
|
+
self.fock = self.read_symmetric_matrix(f, self.n_basis_d)
|
|
863
|
+
|
|
864
|
+
if 'Gross orbital populations:' in line:
|
|
865
|
+
self._read_gross_orb_populations(f)
|
|
866
|
+
break
|
|
867
|
+
|
|
868
|
+
zeros_matrix = np.zeros((self.n_bsuse1, self.n_basis2))
|
|
869
|
+
self.mo1 = np.hstack([self.mo1.T, zeros_matrix])
|
|
870
|
+
zeros_matrix = np.zeros((self.n_bsuse2, self.n_basis1))
|
|
871
|
+
self.mo2 = np.hstack([zeros_matrix, self.mo2.T])
|
|
872
|
+
|
|
873
|
+
if is_matrix:
|
|
874
|
+
print()
|
|
875
|
+
print(' *** Overlap *** ')
|
|
876
|
+
self.print_matrix(self.overlap)
|
|
877
|
+
print()
|
|
878
|
+
print(' *** Fock matrix (alpha) *** ')
|
|
879
|
+
self.print_matrix(self.fock)
|
|
880
|
+
|
|
881
|
+
if output_matrix:
|
|
882
|
+
self.output_csv(f'{self._base_path}_overlap.csv', self.overlap)
|
|
883
|
+
self.output_csv(f'{self._base_path}_fock.csv', self.fock)
|
|
884
|
+
|
|
885
|
+
def run_gaussian(self, gaussian_command, skip_monomer_num=[0]):
|
|
886
|
+
"""Execute gjf files using gaussian.
|
|
887
|
+
|
|
888
|
+
Parameters
|
|
889
|
+
----------
|
|
890
|
+
gaussian_command : str
|
|
891
|
+
Command of gaussian.
|
|
892
|
+
skip_monomer_num: list[int], optional
|
|
893
|
+
If it is 1, skip 1st monomer calculation.
|
|
894
|
+
If it is 2, skip 2nd monomer calculation.
|
|
895
|
+
If it is 3, skip dimer calculation.
|
|
896
|
+
|
|
897
|
+
Returns
|
|
898
|
+
-------
|
|
899
|
+
int
|
|
900
|
+
Returncode of subprocess.run.
|
|
901
|
+
"""
|
|
902
|
+
if 1 in skip_monomer_num:
|
|
903
|
+
print('skip 1st monomer calculation')
|
|
904
|
+
else:
|
|
905
|
+
res = self._execute([gaussian_command, f'{self._base_path}_m1.gjf'], complete_message='1st monomer calculation completed')
|
|
906
|
+
|
|
907
|
+
if res.returncode:
|
|
908
|
+
return res.returncode
|
|
909
|
+
|
|
910
|
+
if 2 in skip_monomer_num:
|
|
911
|
+
print('skip 2nd monomer calculation')
|
|
912
|
+
else:
|
|
913
|
+
res = self._execute([gaussian_command, f'{self._base_path}_m2.gjf'], complete_message='2nd monomer calculation completed')
|
|
914
|
+
|
|
915
|
+
if res.returncode:
|
|
916
|
+
return res.returncode
|
|
917
|
+
|
|
918
|
+
if 3 in skip_monomer_num:
|
|
919
|
+
print('skip dimer calculation')
|
|
920
|
+
else:
|
|
921
|
+
res = self._execute([gaussian_command, f'{self._base_path}.gjf'], complete_message='dimer calculation completed')
|
|
922
|
+
|
|
923
|
+
return res.returncode
|
|
924
|
+
|
|
925
|
+
return 0
|
|
926
|
+
|
|
927
|
+
def _create_dummy_cube_file(self):
|
|
928
|
+
"""Create dummy cube file."""
|
|
929
|
+
min_x = 100
|
|
930
|
+
max_x = -100
|
|
931
|
+
min_y = 100
|
|
932
|
+
max_y = -100
|
|
933
|
+
min_z = 100
|
|
934
|
+
max_z = -100
|
|
935
|
+
cube_delta = 0.5
|
|
936
|
+
bohr = 0.52917721092
|
|
937
|
+
|
|
938
|
+
with open(f'{self._base_path}.gjf', 'r') as f:
|
|
939
|
+
while True:
|
|
940
|
+
line = f.readline()
|
|
941
|
+
if not line:
|
|
942
|
+
break
|
|
943
|
+
if re.search('[-0-9]+ [0-3]', line):
|
|
944
|
+
f, coordinates = self.extract_coordinates(f)
|
|
945
|
+
break
|
|
946
|
+
|
|
947
|
+
for line in coordinates:
|
|
948
|
+
_, x, y, z = line.strip().split()
|
|
949
|
+
x, y, z = map(float, [x, y, z])
|
|
950
|
+
min_x = min(min_x, x)
|
|
951
|
+
max_x = max(max_x, x)
|
|
952
|
+
min_y = min(min_y, y)
|
|
953
|
+
max_y = max(max_y, y)
|
|
954
|
+
min_z = min(min_z, z)
|
|
955
|
+
max_z = max(max_z, z)
|
|
956
|
+
|
|
957
|
+
min_x -= 5.0
|
|
958
|
+
max_x += 5.0
|
|
959
|
+
min_y -= 5.0
|
|
960
|
+
max_y += 5.0
|
|
961
|
+
min_z -= 5.0
|
|
962
|
+
max_z += 5.0
|
|
963
|
+
min_x = math.floor(min_x / cube_delta) * cube_delta
|
|
964
|
+
min_y = math.floor(min_y / cube_delta) * cube_delta
|
|
965
|
+
min_z = math.floor(min_z / cube_delta) * cube_delta
|
|
966
|
+
|
|
967
|
+
num_x = math.ceil((max_x - min_x) / cube_delta)
|
|
968
|
+
num_y = math.ceil((max_y - min_y) / cube_delta)
|
|
969
|
+
num_z = math.ceil((max_z - min_z) / cube_delta)
|
|
970
|
+
|
|
971
|
+
with open(f'{self._base_path}_dummy.cube', 'w') as f:
|
|
972
|
+
f.write('\n\n')
|
|
973
|
+
f.write(f'1, {min_x/bohr:.3f}, {min_y/bohr:.3f}, {min_z/bohr:.3f}\n')
|
|
974
|
+
f.write(f'{num_x}, {cube_delta/bohr:.3f}, 0.0, 0.0\n')
|
|
975
|
+
f.write(f'{num_y}, 0.0, {cube_delta/bohr:.3f}, 0.0\n')
|
|
976
|
+
f.write(f'{num_z}, 0.0, 0.0, {cube_delta/bohr:.3f}\n')
|
|
977
|
+
|
|
978
|
+
def _execute(self, command_list, complete_message='Calculation completed'):
|
|
979
|
+
"""Execute command
|
|
980
|
+
|
|
981
|
+
Parameters
|
|
982
|
+
----------
|
|
983
|
+
command_list : list
|
|
984
|
+
A list of space-separated commands.
|
|
985
|
+
complete_message : str, optional
|
|
986
|
+
The message when the calculation is completed., default 'Calculation completed'
|
|
987
|
+
|
|
988
|
+
Returns
|
|
989
|
+
-------
|
|
990
|
+
CompletedProcess
|
|
991
|
+
Return value of subprocess.run.
|
|
992
|
+
"""
|
|
993
|
+
command = ' '.join(command_list)
|
|
994
|
+
print(f'> {command}')
|
|
995
|
+
|
|
996
|
+
res = subprocess.run(command_list, capture_output=True, text=True)
|
|
997
|
+
|
|
998
|
+
# check error
|
|
999
|
+
if res.returncode:
|
|
1000
|
+
print(f'Failed to execute {command}')
|
|
1001
|
+
else:
|
|
1002
|
+
print(complete_message)
|
|
1003
|
+
base_path = os.path.splitext(command_list[-1])[0]
|
|
1004
|
+
print(f' {base_path}{self._extension_log}')
|
|
1005
|
+
|
|
1006
|
+
return res
|
|
1007
|
+
|
|
1008
|
+
def _read_gross_orb_populations(self, reader):
|
|
1009
|
+
"""Extract atomic orbitals and atomic symbols.
|
|
1010
|
+
|
|
1011
|
+
Parameters
|
|
1012
|
+
----------
|
|
1013
|
+
reader : _io.TextIOWrapper
|
|
1014
|
+
Return value of open function.
|
|
1015
|
+
"""
|
|
1016
|
+
reader.readline()
|
|
1017
|
+
|
|
1018
|
+
self.atom_index = []
|
|
1019
|
+
self.atom_symbol = []
|
|
1020
|
+
self.atom_orbital = []
|
|
1021
|
+
|
|
1022
|
+
idx_num = 1
|
|
1023
|
+
for i in range(1, self.n_basis_d+1):
|
|
1024
|
+
line = reader.readline().strip()
|
|
1025
|
+
# remove number of row
|
|
1026
|
+
line = line[re.match(f'{i}', line).span()[1]:].strip()
|
|
1027
|
+
# check atom
|
|
1028
|
+
change_atom = re.match(f'{idx_num} ', line)
|
|
1029
|
+
|
|
1030
|
+
if change_atom:
|
|
1031
|
+
self.atom_index.append(idx_num-1)
|
|
1032
|
+
line = line[change_atom.span()[1]:].strip()
|
|
1033
|
+
elem = line[:2].strip()
|
|
1034
|
+
self.atom_symbol.append(elem)
|
|
1035
|
+
line = line[re.match(f'{elem}', line).span()[1]:].strip()
|
|
1036
|
+
idx_num += 1
|
|
1037
|
+
else:
|
|
1038
|
+
self.atom_index.append(self.atom_index[-1])
|
|
1039
|
+
self.atom_symbol.append(self.atom_symbol[-1])
|
|
1040
|
+
self.atom_orbital.append(line[:5].strip())
|
|
1041
|
+
|
|
1042
|
+
|
|
1043
|
+
class PairAnalysis:
|
|
1044
|
+
"""Analyze atomic pair transfer integrals."""
|
|
1045
|
+
def __init__(self, apta):
|
|
1046
|
+
"""Inits PairAnalysisClass.
|
|
1047
|
+
|
|
1048
|
+
Parameters
|
|
1049
|
+
----------
|
|
1050
|
+
apta : list
|
|
1051
|
+
List of atomic pair transfer integrals including labels.
|
|
1052
|
+
"""
|
|
1053
|
+
self._labels = []
|
|
1054
|
+
self._a_transfer = []
|
|
1055
|
+
|
|
1056
|
+
for row in apta[1:-1]:
|
|
1057
|
+
symbol = re.sub('[0-9]+', '', row[0])
|
|
1058
|
+
self._labels.append(symbol)
|
|
1059
|
+
self._a_transfer.append(row[1:-1])
|
|
1060
|
+
|
|
1061
|
+
label = apta[0][1:-1]
|
|
1062
|
+
label = list(map(lambda x: re.sub('[0-9]+', '', x), label))
|
|
1063
|
+
|
|
1064
|
+
self._labels.extend(label)
|
|
1065
|
+
self._a_transfer = np.array(self._a_transfer, dtype=np.float64)
|
|
1066
|
+
self.n_atoms1 = self._a_transfer.shape[0]
|
|
1067
|
+
self.n_atoms2 = self._a_transfer.shape[1]
|
|
1068
|
+
|
|
1069
|
+
def print_largest_pairs(self):
|
|
1070
|
+
"""Print largest pairs."""
|
|
1071
|
+
transfer = np.sum(self._a_transfer)
|
|
1072
|
+
a_transfer_flat = self._a_transfer.flatten()
|
|
1073
|
+
sorted_index = np.argsort(a_transfer_flat)
|
|
1074
|
+
print()
|
|
1075
|
+
print('---------------')
|
|
1076
|
+
print(' Largest Pairs ')
|
|
1077
|
+
print('---------------')
|
|
1078
|
+
print(f'rank\tpair{" " * 9}\ttransfer (meV)\tratio (%)')
|
|
1079
|
+
|
|
1080
|
+
rank_list = np.arange(1, len(sorted_index)+1)
|
|
1081
|
+
if len(sorted_index) <= 20:
|
|
1082
|
+
print_index = sorted_index
|
|
1083
|
+
ranks = rank_list
|
|
1084
|
+
else:
|
|
1085
|
+
print_index = np.hstack([sorted_index[:10], sorted_index[-10:]])
|
|
1086
|
+
ranks = np.hstack([rank_list[:10], rank_list[-10:]])
|
|
1087
|
+
|
|
1088
|
+
for i, a_i in enumerate(reversed(print_index)):
|
|
1089
|
+
row_i = a_i // self.n_atoms2
|
|
1090
|
+
col_i = a_i % self.n_atoms2
|
|
1091
|
+
pair = f'{row_i+1}{self._labels[row_i]}' + ' - ' + \
|
|
1092
|
+
f'{col_i+self.n_atoms2+1}{self._labels[col_i]}'
|
|
1093
|
+
ratio = np.divide(a_transfer_flat[a_i], transfer, out=np.array(0.0), where=(transfer!=0)) * 100
|
|
1094
|
+
print(f'{ranks[i]:<4}\t{pair:<13}\t{a_transfer_flat[a_i]:>14}\t{ratio:>9.1f}')
|
|
1095
|
+
|
|
1096
|
+
def print_element_pairs(self):
|
|
1097
|
+
"""Print element pairs."""
|
|
1098
|
+
element_pair = {
|
|
1099
|
+
'H-I': 0.0, 'C-C': 0.0, 'C-H': 0.0, 'C-S': 0.0, 'C-Se': 0.0,
|
|
1100
|
+
'C-I': 0.0, 'S-S': 0.0, 'Se-Se': 0.0, 'N-S': 0.0, 'I-S': 0.0,
|
|
1101
|
+
'I-I': 0.0,
|
|
1102
|
+
}
|
|
1103
|
+
keys = element_pair.keys()
|
|
1104
|
+
|
|
1105
|
+
for i in range(self.n_atoms1):
|
|
1106
|
+
sym1 = self._labels[i]
|
|
1107
|
+
for j in range(self.n_atoms2):
|
|
1108
|
+
sym2 = self._labels[self.n_atoms1 + j]
|
|
1109
|
+
key = '-'.join(sorted([sym1, sym2]))
|
|
1110
|
+
if key in keys:
|
|
1111
|
+
element_pair[key] += self._a_transfer[i][j]
|
|
1112
|
+
|
|
1113
|
+
print()
|
|
1114
|
+
print('---------------')
|
|
1115
|
+
print(' Element Pairs ')
|
|
1116
|
+
print('---------------')
|
|
1117
|
+
for k, value in element_pair.items():
|
|
1118
|
+
if value != 0:
|
|
1119
|
+
print(f'{k:<5}\t{value:>9.3f}')
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
if __name__ == '__main__':
|
|
1123
|
+
main()
|