yu-mcal 0.1.4__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/constants/element_properties.csv +121 -0
- mcal/mcal.py +844 -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.4.dist-info/METADATA +263 -0
- yu_mcal-0.1.4.dist-info/RECORD +15 -0
- yu_mcal-0.1.4.dist-info/WHEEL +4 -0
- yu_mcal-0.1.4.dist-info/entry_points.txt +2 -0
- yu_mcal-0.1.4.dist-info/licenses/LICENSE +21 -0
mcal/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
File without changes
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
"""hopping_mobility_model.py (2025/10/06)"""
|
|
2
|
+
import math
|
|
3
|
+
import random
|
|
4
|
+
from typing import List, Tuple
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from numpy.typing import NDArray
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
const_kb = 1.380649e-23 # Boltzmann constant [J/K]
|
|
11
|
+
const_e = 1.60217663e-19 # Elementary charge [C(=J/eV)]
|
|
12
|
+
const_hbar = 6.62607015e-34 / (2 * math.pi) # Dirac constant [Js]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def demo():
|
|
16
|
+
# 一次元系で、粒子は1 sごとに0.01の確率で右へ1 m、0.01の確率で左へ1 m移動し、0.98の確率でその場に留まる。
|
|
17
|
+
print("\nOne-dimensional system")
|
|
18
|
+
lattice = np.array(((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)))
|
|
19
|
+
hop = ((0, 0, 1, 0, 0, 0.01),)
|
|
20
|
+
D = diffusion_coefficient_tensor(lattice, hop)
|
|
21
|
+
print("Diffusion coefficient tensor (analytical):")
|
|
22
|
+
for d in D:
|
|
23
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
24
|
+
print("Diffusion coefficient tensor (ODE):")
|
|
25
|
+
D_ode = diffusion_coefficient_tensor_ODE(lattice, hop)
|
|
26
|
+
for d in D_ode:
|
|
27
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
28
|
+
print("Diffusion coefficient tensor (MC):")
|
|
29
|
+
D_mc = diffusion_coefficient_tensor_MC(lattice, hop)
|
|
30
|
+
for d in D_mc:
|
|
31
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
32
|
+
|
|
33
|
+
# 一次元系で、粒子は偶数サイトにいる時は1sごとに0.02の確率で右へ1m、0.01の確率で左へ1m移動し、0.97の確率でその場に留まる。
|
|
34
|
+
# 奇数サイトにいる時は1sごとに0.01の確率で右へ1m、0.02の確率で左へ1m移動し、0.97の確率でその場に留まる。
|
|
35
|
+
print("\nOne-dimensional dimer system")
|
|
36
|
+
lattice = np.array(((2.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)))
|
|
37
|
+
hop = (
|
|
38
|
+
(0, 1, 0, 0, 0, 0.02),
|
|
39
|
+
(0, 1, -1, 0, 0, 0.01),
|
|
40
|
+
)
|
|
41
|
+
D = diffusion_coefficient_tensor(lattice, hop)
|
|
42
|
+
print("Diffusion coefficient tensor (analytical):")
|
|
43
|
+
for d in D:
|
|
44
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
45
|
+
print("Diffusion coefficient tensor (ODE):")
|
|
46
|
+
D_ode = diffusion_coefficient_tensor_ODE(lattice, hop)
|
|
47
|
+
for d in D_ode:
|
|
48
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
49
|
+
print("Diffusion coefficient tensor (MC):")
|
|
50
|
+
D_mc = diffusion_coefficient_tensor_MC(lattice, hop)
|
|
51
|
+
for d in D_mc:
|
|
52
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
53
|
+
|
|
54
|
+
# ランダムな条件で検証
|
|
55
|
+
print("\nRandom system")
|
|
56
|
+
lattice = np.random.random((3, 3)) * 2.0
|
|
57
|
+
lattice[0, 1:] = 0.0
|
|
58
|
+
lattice[1, 2:] = 0.0
|
|
59
|
+
hop = []
|
|
60
|
+
for _ in range(6):
|
|
61
|
+
s = random.randint(0, 1)
|
|
62
|
+
t = random.randint(0, 1)
|
|
63
|
+
i = random.randint(-1, 1)
|
|
64
|
+
j = random.randint(-1, 1)
|
|
65
|
+
k = random.randint(-1, 1)
|
|
66
|
+
p = random.random() * 0.02
|
|
67
|
+
hop.append((s, t, i, j, k, p))
|
|
68
|
+
D = diffusion_coefficient_tensor(lattice, hop)
|
|
69
|
+
print("Diffusion coefficient tensor (analytical):")
|
|
70
|
+
for d in D:
|
|
71
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
72
|
+
print("Diffusion coefficient tensor (ODE):")
|
|
73
|
+
D_ode = diffusion_coefficient_tensor_ODE(lattice, hop)
|
|
74
|
+
for d in D_ode:
|
|
75
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
76
|
+
print("Diffusion coefficient tensor (MC):")
|
|
77
|
+
D_mc = diffusion_coefficient_tensor_MC(lattice, hop)
|
|
78
|
+
for d in D_mc:
|
|
79
|
+
print(f"{d[0]:9.6f} {d[1]:9.6f} {d[2]:9.6f}")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def cal_pinv(array: NDArray[np.float64], rcond: float = 1e-3) -> NDArray[np.float64]:
|
|
83
|
+
"""Calculate pseudo-inverse matrix using eigenvalue decomposition
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
array : NDArray[np.float64]
|
|
88
|
+
Input matrix
|
|
89
|
+
rcond : float, optional
|
|
90
|
+
Cutoff for small singular values, by default 1e-9
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
NDArray[np.float64]
|
|
95
|
+
Pseudo-inverse matrix
|
|
96
|
+
|
|
97
|
+
Raises
|
|
98
|
+
------
|
|
99
|
+
ValueError
|
|
100
|
+
The last eigenvalue is not zero.
|
|
101
|
+
ValueError
|
|
102
|
+
All eigenvalues except the last one should be negative.
|
|
103
|
+
"""
|
|
104
|
+
eigvals, eigvecs = np.linalg.eigh(array)
|
|
105
|
+
|
|
106
|
+
# Calculate pseudo-inverse matrix using eigenvalue decomposition
|
|
107
|
+
inveigvals = np.zeros_like(eigvals)
|
|
108
|
+
if abs(eigvals[-1] / eigvals[0]) > rcond:
|
|
109
|
+
raise ValueError(f"The last eigenvalue is not zero, which is unexpected for this test case. {eigvals}")
|
|
110
|
+
if any(eigvals[0:-1] > 0):
|
|
111
|
+
raise ValueError(f"All eigenvalues except the last one should be negative, which is unexpected for this test case. {eigvals}")
|
|
112
|
+
|
|
113
|
+
inveigvals[0:-1] = 1.0 / eigvals[0:-1]
|
|
114
|
+
inveigvals[-1] = 0.0
|
|
115
|
+
pinv = eigvecs @ np.diag(inveigvals) @ eigvecs.T
|
|
116
|
+
|
|
117
|
+
return pinv
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def marcus_rate(transfer: float, reorganization: float, T: float = 300.0) -> float:
|
|
121
|
+
"""Calculate hopping rate (1/s) from transfer integral (eV) and reorganization energy (eV)
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
transfer : float
|
|
126
|
+
Transfer integral [eV]
|
|
127
|
+
reorganization : float
|
|
128
|
+
Reorganization energy [eV]
|
|
129
|
+
T : float
|
|
130
|
+
Temperature [K], by default 300.0
|
|
131
|
+
|
|
132
|
+
Returns
|
|
133
|
+
-------
|
|
134
|
+
float
|
|
135
|
+
Hopping rate [1/s]
|
|
136
|
+
"""
|
|
137
|
+
kbT = const_kb * T
|
|
138
|
+
return (
|
|
139
|
+
(transfer * const_e) ** 2
|
|
140
|
+
/ const_hbar
|
|
141
|
+
* math.sqrt(math.pi / (reorganization * const_e * kbT))
|
|
142
|
+
* math.exp(-reorganization * const_e / (4 * kbT))
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def mobility_tensor(D: NDArray[np.float64], T: float = 300.0) -> NDArray[np.float64]:
|
|
147
|
+
"""Calculate mobility tensor from diffusion coefficient tensor
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
D : 3x3 numpy.array
|
|
152
|
+
Diffusion coefficient tensor
|
|
153
|
+
T : float
|
|
154
|
+
Temperature [K], by default 300.0
|
|
155
|
+
|
|
156
|
+
Returns
|
|
157
|
+
-------
|
|
158
|
+
3x3 numpy.array
|
|
159
|
+
Mobility tensor
|
|
160
|
+
"""
|
|
161
|
+
return D * const_e / (const_kb * T)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def diffusion_coefficient_tensor(
|
|
165
|
+
lattice: NDArray[np.float64],
|
|
166
|
+
hop: List[Tuple[int, int, int, int, int, float]]
|
|
167
|
+
) -> NDArray[np.float64]:
|
|
168
|
+
"""Calculate diffusion coefficient tensor from hopping rate
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
lattice : 3x3 numpy.array
|
|
173
|
+
lattice[0,:] is a-axis vector, lattice[1,:] b-axis vector, lattice[2,:] c-axis vector
|
|
174
|
+
hop : list of (int, int, int, int, int, float) tuple.
|
|
175
|
+
(s, t, i, j, k, p) means that the hopping rate from s-th molecule in (0, 0, 0) cell to t-th molecule in (i, j, k) cell is p.
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
3x3 numpy.array
|
|
180
|
+
Diffusion coefficient tensor
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
# Standardize hop list
|
|
184
|
+
hop = _standardize_hop_list(hop)
|
|
185
|
+
|
|
186
|
+
# Number of molecules in the unit cell
|
|
187
|
+
n = len(set([h[0] for h in hop]) | set([h[1] for h in hop]))
|
|
188
|
+
|
|
189
|
+
# Prepare arrays
|
|
190
|
+
D = np.zeros((3, 3))
|
|
191
|
+
B = np.zeros((n, n))
|
|
192
|
+
C = np.zeros((n, 3))
|
|
193
|
+
|
|
194
|
+
for s, t, i, j, k, p in hop:
|
|
195
|
+
vec = np.array((i, j, k)) @ lattice
|
|
196
|
+
D[:, :] += p * np.outer(vec, vec) * 2 # Consider hopping in both directions
|
|
197
|
+
B[s, t] += p
|
|
198
|
+
B[t, s] += p # Consider hopping in both directions
|
|
199
|
+
B[s, s] -= p
|
|
200
|
+
B[t, t] -= p
|
|
201
|
+
C[s, :] += p * vec
|
|
202
|
+
C[t, :] -= p * vec # Consider hopping in both directions
|
|
203
|
+
|
|
204
|
+
# For n = 1 case, skip C.T @ B_pinv @ C term as it equals zero
|
|
205
|
+
if n > 1:
|
|
206
|
+
B_pinv = cal_pinv(B)
|
|
207
|
+
D = (D / 2 + C.T @ B_pinv @ C) / n
|
|
208
|
+
else:
|
|
209
|
+
D = D / 2
|
|
210
|
+
|
|
211
|
+
# Check computational errors
|
|
212
|
+
threshold = np.max(abs(D)) * 1e-6
|
|
213
|
+
D_diff = abs(D - D.T)
|
|
214
|
+
if np.any(D_diff > threshold):
|
|
215
|
+
raise ValueError(f"Diffusion coefficient tensor D should be symmetric: {D}")
|
|
216
|
+
|
|
217
|
+
# Make symmetric matrix considering computational errors
|
|
218
|
+
D = (D + D.T) / 2
|
|
219
|
+
|
|
220
|
+
return D
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def diffusion_coefficient_tensor_ODE(
|
|
224
|
+
lattice: NDArray[np.float64],
|
|
225
|
+
hop: List[Tuple[int, int, int, int, int, float]],
|
|
226
|
+
max_steps: int = 200,
|
|
227
|
+
size: int = 40,
|
|
228
|
+
max_rate: float = 0.05
|
|
229
|
+
) -> NDArray[np.float64]:
|
|
230
|
+
"""Calculate diffusion coefficient tensor from numerical solution of Ordinary Differential Equation (ODE)
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
lattice : 3x3 numpy.array
|
|
235
|
+
lattice[0,:] is a-axis vector, lattice[1,:] b-axis vector, lattice[2,:] c-axis vector
|
|
236
|
+
hop : list of (int, int, int, int, int, float) tuple.
|
|
237
|
+
(s, t, i, j, k, p) means that the hopping rate from s-th molecule in (0, 0, 0) cell to t-th molecule in (i, j, k) cell is p.
|
|
238
|
+
max_steps : int
|
|
239
|
+
Maximum number of steps
|
|
240
|
+
size : int
|
|
241
|
+
Size of the simulation box
|
|
242
|
+
max_rate : float
|
|
243
|
+
Maximum rate of hopping
|
|
244
|
+
|
|
245
|
+
Returns
|
|
246
|
+
-------
|
|
247
|
+
3x3 numpy.array
|
|
248
|
+
Diffusion coefficient tensor
|
|
249
|
+
"""
|
|
250
|
+
# Standardize hop list
|
|
251
|
+
hop = _standardize_hop_list(hop)
|
|
252
|
+
|
|
253
|
+
# Number of molecules in the unit cell
|
|
254
|
+
n = len(set([h[0] for h in hop]) | set([h[1] for h in hop]))
|
|
255
|
+
|
|
256
|
+
prob = np.zeros((2 * size, 2 * size, 2 * size, n))
|
|
257
|
+
prob[size, size, size, :] = 1.0 / n
|
|
258
|
+
pre_prob = np.zeros_like(prob)
|
|
259
|
+
|
|
260
|
+
dt = max_rate / max(h[5] for h in hop) # Time step
|
|
261
|
+
for _ in range(max_steps):
|
|
262
|
+
pre_prob[:, :, :, :] = prob
|
|
263
|
+
for s, t, i, j, k, p in hop:
|
|
264
|
+
prob[:, :, :, t] += np.roll(pre_prob[:, :, :, s], (i, j, k), axis=(0, 1, 2)) * p * dt
|
|
265
|
+
prob[:, :, :, s] += np.roll(pre_prob[:, :, :, t], (-i, -j, -k), axis=(0, 1, 2)) * p * dt
|
|
266
|
+
prob[:, :, :, s] -= pre_prob[:, :, :, s] * p * dt
|
|
267
|
+
prob[:, :, :, t] -= pre_prob[:, :, :, t] * p * dt
|
|
268
|
+
|
|
269
|
+
# Check the sum of probabilities
|
|
270
|
+
total_prob = np.sum(prob)
|
|
271
|
+
assert np.isclose(total_prob, 1.0), f"Total probability is not 1: {total_prob}"
|
|
272
|
+
|
|
273
|
+
# Average of outer products of positions
|
|
274
|
+
avg_outer_product = np.zeros((3, 3))
|
|
275
|
+
for i, j, k, l in np.ndindex(2 * size, 2 * size, 2 * size, n):
|
|
276
|
+
vec = np.array((i - size, j - size, k - size)) @ lattice
|
|
277
|
+
avg_outer_product += prob[i, j, k, l] * np.outer(vec, vec)
|
|
278
|
+
|
|
279
|
+
D = avg_outer_product / (2 * max_steps * dt)
|
|
280
|
+
return D
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def diffusion_coefficient_tensor_MC(
|
|
284
|
+
lattice: NDArray[np.float64],
|
|
285
|
+
hop: List[Tuple[int, int, int, int, int, float]],
|
|
286
|
+
steps: int = 100,
|
|
287
|
+
particles: int = 10000
|
|
288
|
+
) -> NDArray[np.float64]:
|
|
289
|
+
"""Calculate diffusion coefficient tensor from Monte Carlo simulation using Gillespie algorithm.
|
|
290
|
+
|
|
291
|
+
Parameters
|
|
292
|
+
----------
|
|
293
|
+
lattice : 3x3 numpy.array
|
|
294
|
+
lattice[0,:] is a-axis vector, lattice[1,:] b-axis vector, lattice[2,:] c-axis vector
|
|
295
|
+
hop : list of (int, int, int, int, int, float) tuple.
|
|
296
|
+
(s, t, i, j, k, p) means that the hopping rate from s-th molecule in (0, 0, 0) cell to t-th molecule in (i, j, k) cell is p.
|
|
297
|
+
steps : int
|
|
298
|
+
Number of steps
|
|
299
|
+
particles : int
|
|
300
|
+
Number of particles
|
|
301
|
+
|
|
302
|
+
Returns
|
|
303
|
+
-------
|
|
304
|
+
3x3 numpy.array
|
|
305
|
+
Diffusion coefficient tensor
|
|
306
|
+
"""
|
|
307
|
+
# Standardize hop list
|
|
308
|
+
hop = _standardize_hop_list(hop)
|
|
309
|
+
|
|
310
|
+
# Number of molecules in the unit cell
|
|
311
|
+
n = len(set([h[0] for h in hop]) | set([h[1] for h in hop]))
|
|
312
|
+
|
|
313
|
+
paths = [[] for _ in range(n)]
|
|
314
|
+
probs = [[] for _ in range(n)]
|
|
315
|
+
total_rates = [0] * n
|
|
316
|
+
for s, t, i, j, k, p in hop:
|
|
317
|
+
paths[s].append((t, i, j, k))
|
|
318
|
+
paths[t].append((s, -i, -j, -k))
|
|
319
|
+
probs[s].append(p)
|
|
320
|
+
probs[t].append(p)
|
|
321
|
+
total_rates[s] += p
|
|
322
|
+
total_rates[t] += p
|
|
323
|
+
max_time = steps / np.mean(total_rates)
|
|
324
|
+
|
|
325
|
+
# Simulation
|
|
326
|
+
sum_outer_product = np.zeros((3, 3))
|
|
327
|
+
for _ in range(particles):
|
|
328
|
+
xyz = np.zeros(3, dtype=int)
|
|
329
|
+
mol = random.choice(range(n)) # ランダムに初期位置を選ぶ
|
|
330
|
+
t = 0.0
|
|
331
|
+
while t < max_time:
|
|
332
|
+
t += -math.log(1.0 - random.random()) / total_rates[mol]
|
|
333
|
+
path = random.choices(paths[mol], weights=probs[mol])[0]
|
|
334
|
+
xyz += path[1], path[2], path[3]
|
|
335
|
+
mol = path[0]
|
|
336
|
+
xyz = xyz @ lattice
|
|
337
|
+
sum_outer_product += np.outer(xyz, xyz)
|
|
338
|
+
|
|
339
|
+
# Calculate diffusion coefficient
|
|
340
|
+
D = sum_outer_product / (particles * 2 * max_time)
|
|
341
|
+
return D
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def print_tensor(tensor: NDArray[np.float64], msg: str = 'Mobility tensor'):
|
|
345
|
+
print('-' * (len(msg)+2))
|
|
346
|
+
print(f' {msg} ')
|
|
347
|
+
print('-' * (len(msg)+2))
|
|
348
|
+
if tensor.shape == (3, ):
|
|
349
|
+
print(f"{tensor[0]:12.6g} {tensor[1]:12.6g} {tensor[2]:12.6g}")
|
|
350
|
+
elif tensor.shape == (3, 3):
|
|
351
|
+
for a in tensor:
|
|
352
|
+
print(f"{a[0]:12.6g} {a[1]:12.6g} {a[2]:12.6g}")
|
|
353
|
+
print()
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _standardize_hop_list(hop: List[Tuple[int, int, int, int, int, float]]) -> List[Tuple[int, int, int, int, int, float]]:
|
|
357
|
+
"""
|
|
358
|
+
Standardize the hop list by ensuring that s <= t.
|
|
359
|
+
If s == t, ensure that the first non-zero component of (i, j, k) is positive.
|
|
360
|
+
|
|
361
|
+
Parameters
|
|
362
|
+
----------
|
|
363
|
+
hop: list of (int, int, int, int, int, float) tuples.
|
|
364
|
+
(s, t, i, j, k, p) means that the hopping rate from s-th molecule in (0, 0, 0) cell to t-th molecule in (i, j, k) cell is p.
|
|
365
|
+
|
|
366
|
+
Returns
|
|
367
|
+
-------
|
|
368
|
+
list of (int, int, int, int, int, float) tuple.
|
|
369
|
+
List of standardized hopping rate tuples.
|
|
370
|
+
"""
|
|
371
|
+
hop = list(hop)
|
|
372
|
+
standardized_hop = []
|
|
373
|
+
standardized_hop_keys = set()
|
|
374
|
+
for s, t, i, j, k, p in hop:
|
|
375
|
+
if s > t:
|
|
376
|
+
s, t, i, j, k, p = t, s, -i, -j, -k, p
|
|
377
|
+
elif s == t:
|
|
378
|
+
if (i, j, k) == (0, 0, 0):
|
|
379
|
+
continue
|
|
380
|
+
elif i < 0 or (i == 0 and (j < 0 or (j == 0 and k < 0))):
|
|
381
|
+
s, t, i, j, k, p = t, s, -i, -j, -k, p
|
|
382
|
+
|
|
383
|
+
if (s, t, i, j, k) not in standardized_hop_keys:
|
|
384
|
+
standardized_hop_keys.add((s, t, i, j, k))
|
|
385
|
+
standardized_hop.append((s, t, i, j, k, p))
|
|
386
|
+
|
|
387
|
+
return standardized_hop
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
if __name__ == "__main__":
|
|
391
|
+
demo()
|