mdkits 0.1.13__py3-none-any.whl → 1.2.3__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.
- mdkits/build_cli/adsorbate.py +2 -1
- mdkits/build_cli/build_bulk.py +2 -1
- mdkits/build_cli/build_interface.py +5 -0
- mdkits/build_cli/build_solution.py +48 -4
- mdkits/build_cli/build_surface.py +2 -5
- mdkits/build_cli/cut_surface.py +1 -1
- mdkits/build_cli/supercell.py +1 -1
- mdkits/cli/convert.py +1 -1
- mdkits/cli/extract.py +29 -18
- mdkits/{cli → dft_cli/.back}/pdos.py +1 -0
- mdkits/dft_cli/check_neb.py +0 -0
- mdkits/{cli → dft_cli}/cube.py +3 -2
- mdkits/dft_cli/dft_cli.py +23 -0
- mdkits/dft_cli/fix.py +54 -0
- mdkits/dft_cli/pdos.py +119 -0
- mdkits/md_cli/angle.py +122 -0
- mdkits/{cli → md_cli}/density.py +24 -19
- mdkits/md_cli/dipole.py +124 -0
- mdkits/md_cli/hb_distribution.py +185 -0
- mdkits/md_cli/md_cli.py +32 -0
- mdkits/md_cli/monitor.py +104 -0
- mdkits/md_cli/msd.py +44 -0
- mdkits/md_cli/rdf.py +53 -0
- mdkits/md_cli/setting.py +14 -0
- mdkits/md_cli/vac.py +65 -0
- mdkits/{cli → md_cli}/wrap.py +4 -3
- mdkits/mdkits.py +5 -9
- mdkits/util/.fig_operation.py.swp +0 -0
- mdkits/util/arg_type.py +18 -8
- mdkits/util/cp2k_input_parsing.py +5 -1
- mdkits/util/encapsulated_ase.py +28 -7
- mdkits/util/encapsulated_mda.py +4 -1
- mdkits/util/numpy_geo.py +10 -5
- mdkits/util/os_operation.py +3 -1
- mdkits/util/out_err.py +18 -6
- mdkits-1.2.3.dist-info/METADATA +370 -0
- mdkits-1.2.3.dist-info/RECORD +51 -0
- mdkits/cli/,hb_distribution_down.py +0 -114
- mdkits/cli/hartree_potential.py +0 -59
- mdkits/cli/hartree_potential_ave.py +0 -84
- mdkits/cli/hb.py +0 -101
- mdkits/cli/hb_distribution.py +0 -126
- mdkits/cli/packmol_input.py +0 -76
- mdkits-0.1.13.dist-info/METADATA +0 -226
- mdkits-0.1.13.dist-info/RECORD +0 -43
- {mdkits-0.1.13.dist-info → mdkits-1.2.3.dist-info}/LICENSE +0 -0
- {mdkits-0.1.13.dist-info → mdkits-1.2.3.dist-info}/WHEEL +0 -0
- {mdkits-0.1.13.dist-info → mdkits-1.2.3.dist-info}/entry_points.txt +0 -0
mdkits/build_cli/adsorbate.py
CHANGED
|
@@ -18,6 +18,7 @@ from mdkits.util import arg_type, encapsulated_ase, out_err
|
|
|
18
18
|
@click.option('--offset', type=click.Tuple([float, float]), help='adjust site', default=(0, 0), show_default=True)
|
|
19
19
|
@click.option("--cover", type=int, help='cover the surface with adsorbate randomly')
|
|
20
20
|
def main(atoms, adsorbate, cell, select, height, rotate, offset, cover):
|
|
21
|
+
"""add adsorbate molcule to the surface"""
|
|
21
22
|
if height is None:
|
|
22
23
|
raise ValueError("height is required")
|
|
23
24
|
|
|
@@ -44,7 +45,7 @@ def main(atoms, adsorbate, cell, select, height, rotate, offset, cover):
|
|
|
44
45
|
|
|
45
46
|
atoms.write(output_filename, format='cif')
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
out_err.path_output(output_filename)
|
|
48
49
|
|
|
49
50
|
|
|
50
51
|
if __name__ == '__main__':
|
mdkits/build_cli/build_bulk.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import click, os
|
|
4
4
|
from ase.build import bulk
|
|
5
5
|
import numpy as np
|
|
6
|
+
from mdkits.util import out_err
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
@click.command(name='bulk')
|
|
@@ -29,7 +30,7 @@ def main(symbol, cs, a, b, c, alpha, covera, u, orth, cubic):
|
|
|
29
30
|
|
|
30
31
|
o = f"{symbol}_{cs}.cif"
|
|
31
32
|
atoms.write(o, format='cif')
|
|
32
|
-
|
|
33
|
+
out_err.path_output(o)
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
if __name__ == '__main__':
|
|
@@ -23,6 +23,9 @@ def main(slab, sol, interval, cap, vacuum):
|
|
|
23
23
|
slab_copy = slab.copy()
|
|
24
24
|
|
|
25
25
|
sol_cell = sol.cell.cellpar()
|
|
26
|
+
|
|
27
|
+
interface_cell = [max(slab_cell[0], sol_cell[0]), max(slab_cell[1], sol_cell[1]), max(slab_cell[2], sol_cell[2]), max(slab_cell[3], sol_cell[3]), max(slab_cell[4], sol_cell[4]), max(slab_cell[5], sol_cell[5])]
|
|
28
|
+
|
|
26
29
|
sol.set_pbc(True)
|
|
27
30
|
sol.center()
|
|
28
31
|
sol.positions[:, 2] += slab_cell[2] + interval
|
|
@@ -62,6 +65,8 @@ def main(slab, sol, interval, cap, vacuum):
|
|
|
62
65
|
slab.set_cell(slab_cell)
|
|
63
66
|
slab.center()
|
|
64
67
|
|
|
68
|
+
slab_cell[0] = interface_cell[0]
|
|
69
|
+
slab_cell[1] = interface_cell[1]
|
|
65
70
|
if vacuum > 0:
|
|
66
71
|
slab_cell[2] += vacuum
|
|
67
72
|
slab.set_cell(slab_cell)
|
|
@@ -2,29 +2,70 @@ import click, os
|
|
|
2
2
|
from mdkits.util import arg_type
|
|
3
3
|
from importlib import resources
|
|
4
4
|
from mdkits.cli import convert
|
|
5
|
-
import tempfile
|
|
5
|
+
import tempfile, platform, subprocess, sys
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
@click.command(name="solution")
|
|
9
9
|
@click.argument("filename", type=click.Path(exists=True), nargs=-1)
|
|
10
10
|
@click.option('--infile', is_flag=True, help="read input mode")
|
|
11
|
-
@click.option('--
|
|
11
|
+
@click.option('--install_julia', is_flag=True, help="install julia for system")
|
|
12
|
+
@click.option('--install_packmol', is_flag=True, help="init julia install packmol")
|
|
12
13
|
@click.option('--water_number', type=int, help="number of water molecules", default=0, show_default=True)
|
|
13
14
|
@click.option('-n', type=int, multiple=True, help="number of molecules")
|
|
14
15
|
@click.option('--tolerance', type=float, help="tolerance of solution", default=3.5, show_default=True)
|
|
15
16
|
@click.option('--cell', type=arg_type.Cell, help="set cell, a list of lattice: --cell x,y,z or x,y,z,a,b,c")
|
|
16
17
|
@click.option('--gap', type=float, help="gap between solution and cell", default=1, show_default=True)
|
|
17
|
-
def main(filename, infile,
|
|
18
|
+
def main(filename, infile, install_julia, install_packmol, water_number, n, tolerance, cell, gap):
|
|
18
19
|
"""
|
|
19
20
|
build solution model
|
|
20
21
|
"""
|
|
21
|
-
|
|
22
|
+
os_name = platform.system()
|
|
23
|
+
|
|
24
|
+
if install_julia:
|
|
25
|
+
if os_name in ("Darwin", "Linux", "FreeBSD"):
|
|
26
|
+
try:
|
|
27
|
+
subprocess.run(
|
|
28
|
+
["curl -fsSL https://install.julialang.org | sh"],
|
|
29
|
+
shell=True,
|
|
30
|
+
check=True,
|
|
31
|
+
)
|
|
32
|
+
print("Julia installed (macOS, Linux, FreeBSD).")
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
except subprocess.CalledProcessError as e:
|
|
35
|
+
print(f"Julia install failed: {e}")
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
elif os_name == "Windows":
|
|
38
|
+
try:
|
|
39
|
+
subprocess.run(
|
|
40
|
+
["winget", "install", "--name", "Julia", "--id", "9NJNWW8PVKMN", "-e", "-s", "msstore"],
|
|
41
|
+
check=True,
|
|
42
|
+
)
|
|
43
|
+
print("Julia installed (Windows).")
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
except subprocess.CalledProcessError as e:
|
|
46
|
+
print(f"Julia install failed: {e}")
|
|
47
|
+
sys.exit(0)
|
|
48
|
+
else:
|
|
49
|
+
print(f"unsupport os: {os_name}")
|
|
50
|
+
sys.exit(0)
|
|
51
|
+
|
|
52
|
+
if install_packmol:
|
|
22
53
|
import julia
|
|
23
54
|
julia.install()
|
|
55
|
+
|
|
56
|
+
from julia.api import Julia
|
|
57
|
+
jl = Julia(compiled_modules=False)
|
|
58
|
+
|
|
59
|
+
from julia import Pkg, Main
|
|
60
|
+
Pkg.add("PyCall")
|
|
24
61
|
Pkg.activate("Packmol", shared=True)
|
|
25
62
|
Pkg.add("Packmol")
|
|
26
63
|
Main.exit()
|
|
27
64
|
else:
|
|
65
|
+
if os_name in ("Darwin", "Linux"):
|
|
66
|
+
from julia.api import Julia
|
|
67
|
+
jl = Julia(compiled_modules=False)
|
|
68
|
+
|
|
28
69
|
from julia import Pkg, Main
|
|
29
70
|
|
|
30
71
|
if cell is None:
|
|
@@ -33,6 +74,9 @@ def main(filename, infile, install, water_number, n, tolerance, cell, gap):
|
|
|
33
74
|
if len(filename) == 0 and water_number == 0:
|
|
34
75
|
raise ValueError("at least one file should be provided, or water_number should be greater than 0")
|
|
35
76
|
|
|
77
|
+
if os_name in ("Darwin", "Linux"):
|
|
78
|
+
Pkg.activate("Packmol", shared=True)
|
|
79
|
+
|
|
36
80
|
while True:
|
|
37
81
|
try:
|
|
38
82
|
Main.using("Packmol")
|
|
@@ -22,10 +22,7 @@ def surface_check(obj, surface_type):
|
|
|
22
22
|
@click.option('--orth', is_flag=True, help='if specified and true, forces the creation of a unit cell with orthogonal basis vectors. if the default is such a unit cell, this argument is not supported')
|
|
23
23
|
@click.option('--vacuum', type=float, help='designate vacuum of surface, default is None', default=0.1, show_default=True)
|
|
24
24
|
def main(symbol, surface, size, kind, a, c, thickness, orth, vacuum):
|
|
25
|
-
|
|
26
|
-
# a = args.a * 0.7071 * 2
|
|
27
|
-
#else:
|
|
28
|
-
# a = args.a
|
|
25
|
+
"""build a common surface"""
|
|
29
26
|
|
|
30
27
|
vacuum = vacuum / 2
|
|
31
28
|
build_surface = surface_check(build, surface)
|
|
@@ -53,7 +50,7 @@ def main(symbol, surface, size, kind, a, c, thickness, orth, vacuum):
|
|
|
53
50
|
|
|
54
51
|
out_err.check_cell(atoms)
|
|
55
52
|
atoms.write(out_filename)
|
|
56
|
-
|
|
53
|
+
out_err.path_output(out_filename)
|
|
57
54
|
|
|
58
55
|
|
|
59
56
|
if __name__ == '__main__':
|
mdkits/build_cli/cut_surface.py
CHANGED
|
@@ -70,7 +70,7 @@ def main(atoms, face, vacuum, size, cell, orth):
|
|
|
70
70
|
super_surface = supercell.supercell(surface, size[0], size[1], 1)
|
|
71
71
|
|
|
72
72
|
super_surface.write(o)
|
|
73
|
-
out_err.cell_output(super_surface
|
|
73
|
+
out_err.cell_output(super_surface)
|
|
74
74
|
out_err.path_output(o)
|
|
75
75
|
|
|
76
76
|
|
mdkits/build_cli/supercell.py
CHANGED
mdkits/cli/convert.py
CHANGED
mdkits/cli/extract.py
CHANGED
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
import click
|
|
7
|
-
from mdkits.util import os_operation, arg_type
|
|
7
|
+
from mdkits.util import os_operation, arg_type, out_err
|
|
8
8
|
import MDAnalysis
|
|
9
9
|
from MDAnalysis import Universe
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def write_to_xyz(u, frames, o, cut=None):
|
|
13
|
-
|
|
12
|
+
def write_to_xyz(u, frames, o, select, cut=None):
|
|
13
|
+
ag = u.select_atoms(select)
|
|
14
|
+
with MDAnalysis.Writer(o, ag.atoms.n_atoms, format='XYZ') as w:
|
|
14
15
|
for ts in u.trajectory:
|
|
15
16
|
if ts.frame in frames:
|
|
16
|
-
w.write(
|
|
17
|
+
w.write(ag)
|
|
17
18
|
if cut:
|
|
18
19
|
with open(o, 'r') as fi, open(o+'t', 'w') as fo:
|
|
19
20
|
for i, line in enumerate(fi):
|
|
@@ -22,13 +23,18 @@ def write_to_xyz(u, frames, o, cut=None):
|
|
|
22
23
|
os.replace(o+'t', o)
|
|
23
24
|
|
|
24
25
|
|
|
25
|
-
def write_to_xyz_s(u, frames, cut=None):
|
|
26
|
+
def write_to_xyz_s(u, frames, select, cut=None):
|
|
26
27
|
index = 0
|
|
28
|
+
ag = u.select_atoms(select)
|
|
29
|
+
if select:
|
|
30
|
+
dir = f'./coord/{"_".join(select.split())}'
|
|
31
|
+
else:
|
|
32
|
+
dir = './coord/all'
|
|
27
33
|
for ts in u.trajectory:
|
|
28
34
|
if ts.frame in frames:
|
|
29
|
-
o = f'
|
|
30
|
-
with MDAnalysis.Writer(o,
|
|
31
|
-
w.write(
|
|
35
|
+
o = f'{dir}/coord_{index:03d}'
|
|
36
|
+
with MDAnalysis.Writer(o, ag.atoms.n_atoms, format='XYZ') as w:
|
|
37
|
+
w.write(ag)
|
|
32
38
|
index += 1
|
|
33
39
|
if cut:
|
|
34
40
|
with open(o, 'r') as fi, open(o+'t', 'w') as fo:
|
|
@@ -39,10 +45,10 @@ def write_to_xyz_s(u, frames, cut=None):
|
|
|
39
45
|
|
|
40
46
|
@click.command(name='extract')
|
|
41
47
|
@click.argument('input_file_name', type=click.Path(exists=True), default=os_operation.default_file_name('*-pos-1.xyz', last=True))
|
|
42
|
-
@click.option('-o', type=str, help='output file name', default='extracted.xyz', show_default=True)
|
|
43
48
|
@click.option('-r', type=arg_type.FrameRange, help='frame range to slice', default='-1', show_default=True)
|
|
44
49
|
@click.option('-c', help='output a coord.xyz', is_flag=True)
|
|
45
|
-
|
|
50
|
+
@click.option("--select", type=str, help="select atoms to extract", default="all", show_default=True)
|
|
51
|
+
def main(input_file_name, r, c, select):
|
|
46
52
|
"""
|
|
47
53
|
extract frames in trajectory file
|
|
48
54
|
"""
|
|
@@ -63,17 +69,22 @@ def main(input_file_name, o, r, c):
|
|
|
63
69
|
cut = None
|
|
64
70
|
|
|
65
71
|
if len(r) == 3 and r[-1] is not None:
|
|
66
|
-
if
|
|
67
|
-
|
|
72
|
+
if select:
|
|
73
|
+
dir = f'./coord/{"_".join(select.split())}'
|
|
74
|
+
else:
|
|
75
|
+
dir = './coord/all'
|
|
76
|
+
if not os.path.exists(dir):
|
|
77
|
+
os.makedirs(dir)
|
|
68
78
|
else:
|
|
69
79
|
import shutil
|
|
70
|
-
shutil.rmtree(
|
|
71
|
-
os.makedirs(
|
|
72
|
-
write_to_xyz_s(u, frames, cut=cut)
|
|
73
|
-
click.echo(os.path.abspath(
|
|
80
|
+
shutil.rmtree(dir)
|
|
81
|
+
os.makedirs(dir)
|
|
82
|
+
write_to_xyz_s(u, frames, select, cut=cut)
|
|
83
|
+
click.echo(os.path.abspath(dir))
|
|
74
84
|
else:
|
|
75
|
-
|
|
76
|
-
|
|
85
|
+
o = f"{os.path.basename(u.filename).split('.')[0]}_{'_'.join([str(i) for i in r])}_{'_'.join(select.split()) if select else 'all'}.xyz"
|
|
86
|
+
write_to_xyz(u, frames, o, select, cut=cut)
|
|
87
|
+
out_err.path_output(o)
|
|
77
88
|
|
|
78
89
|
|
|
79
90
|
if __name__ == '__main__':
|
|
@@ -12,6 +12,7 @@ from mdkits.util import os_operation
|
|
|
12
12
|
@click.option('-t', '--type', type=str, default='total', show_default=True)
|
|
13
13
|
@click.option('-c', '--clos', type=tuple)
|
|
14
14
|
def main(filename, type, clos):
|
|
15
|
+
"""analysis cp2k pdos file"""
|
|
15
16
|
if type == 'total':
|
|
16
17
|
dos_obj = Cp2kPdos(filename[0])
|
|
17
18
|
dos, ener = dos_obj.get_raw_dos(dos_type=type)
|
|
File without changes
|
mdkits/{cli → dft_cli}/cube.py
RENAMED
|
@@ -14,14 +14,15 @@ def ave_cube_data(cube_data, range):
|
|
|
14
14
|
|
|
15
15
|
@click.command(name='cube')
|
|
16
16
|
@click.argument('filename', type=click.Path(exists=True), default=os_operation.default_file_name('*.cube', last=True))
|
|
17
|
+
@click.argument('axis', type=click.Choice(['x','y','z', '0','1','2']), default='z')
|
|
17
18
|
@click.option('-b', '--bulk_range', type=(float, float), help='parameter to calculate mean value of bulk', default=None)
|
|
18
19
|
@click.option('-o', type=str, help='output file name, default is "cube.out"', default='cube.out', show_default=True)
|
|
19
|
-
def main(filename, bulk_range, o):
|
|
20
|
+
def main(filename, axis, bulk_range, o):
|
|
20
21
|
"""
|
|
21
22
|
analysis cube file
|
|
22
23
|
"""
|
|
23
24
|
|
|
24
|
-
cube_data = encapsulated_ase.ave_cube(filename)
|
|
25
|
+
cube_data = encapsulated_ase.ave_cube(filename, axis)
|
|
25
26
|
|
|
26
27
|
## if bulk range is exit, out put a difference of cube_data
|
|
27
28
|
if bulk_range is not None:
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from mdkits.dft_cli import (
|
|
3
|
+
cube,
|
|
4
|
+
pdos,
|
|
5
|
+
fix,
|
|
6
|
+
)
|
|
7
|
+
from mdkits.dft_cli import pdos
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group(name='dft')
|
|
11
|
+
@click.pass_context
|
|
12
|
+
def main(ctx):
|
|
13
|
+
"""kits for dft analysis"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
main.add_command(cube.main)
|
|
18
|
+
main.add_command(pdos.main)
|
|
19
|
+
main.add_command(fix.main)
|
|
20
|
+
main.add_command(pdos.main)
|
|
21
|
+
|
|
22
|
+
if __name__ == '__main__':
|
|
23
|
+
main()
|
mdkits/dft_cli/fix.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from MDAnalysis import Universe
|
|
3
|
+
from mdkits.util import encapsulated_ase, os_operation
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.command(name='fix')
|
|
7
|
+
@click.argument('filename', type=click.Path(exists=True), default='./coord.xyz')
|
|
8
|
+
@click.argument('group', type=str)
|
|
9
|
+
@click.option('-o', type=str, help='output file name, default is "fix.inc"', default='fix.inc', show_default=True)
|
|
10
|
+
def main(filename, group, o):
|
|
11
|
+
"""
|
|
12
|
+
generate fix.inc file for cp2k
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
with open(filename, 'r', encoding='utf-8') as fh:
|
|
16
|
+
lines = fh.read().splitlines()
|
|
17
|
+
first = lines[0].strip() if lines else ''
|
|
18
|
+
if not first.isdigit():
|
|
19
|
+
atoms_number = sum(1 for l in lines if l.strip())
|
|
20
|
+
s = '\n'.join([str(atoms_number), ''] + lines) + '\n'
|
|
21
|
+
import io
|
|
22
|
+
virtual_file = io.StringIO(s)
|
|
23
|
+
atoms = Universe(virtual_file, format='xyz').select_atoms(group)
|
|
24
|
+
else:
|
|
25
|
+
atoms = Universe(filename).select_atoms(group)
|
|
26
|
+
indices = atoms.indices + 1
|
|
27
|
+
fix_number = len(indices)
|
|
28
|
+
fix_output = f"{fix_number} atoms have been fixed."
|
|
29
|
+
|
|
30
|
+
arr = sorted(set(int(i) for i in indices))
|
|
31
|
+
if not arr:
|
|
32
|
+
list_str = ""
|
|
33
|
+
else:
|
|
34
|
+
ranges = []
|
|
35
|
+
start = prev = arr[0]
|
|
36
|
+
for x in arr[1:]:
|
|
37
|
+
if x == prev + 1:
|
|
38
|
+
prev = x
|
|
39
|
+
else:
|
|
40
|
+
if start == prev:
|
|
41
|
+
ranges.append(str(start))
|
|
42
|
+
else:
|
|
43
|
+
ranges.append(f"{start}..{prev}")
|
|
44
|
+
start = prev = x
|
|
45
|
+
|
|
46
|
+
if start == prev:
|
|
47
|
+
ranges.append(str(start))
|
|
48
|
+
else:
|
|
49
|
+
ranges.append(f"{start}..{prev}")
|
|
50
|
+
list_str = "# " + group + "\n" "# " + fix_output + "\n" + "LIST " + " ".join(ranges)
|
|
51
|
+
|
|
52
|
+
print(list_str)
|
|
53
|
+
with open(o, 'w') as f:
|
|
54
|
+
f.write(list_str + '\n')
|
mdkits/dft_cli/pdos.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from pymatgen.io.cp2k import outputs
|
|
2
|
+
import click
|
|
3
|
+
from mdkits.util import arg_type
|
|
4
|
+
from pymatgen.electronic_structure.core import Spin
|
|
5
|
+
import pandas as pd
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def calculate_gaussian_dos(E_grid, eigenvalues, weights, sigma):
|
|
10
|
+
"""
|
|
11
|
+
使用高斯函数对离散能级进行展宽计算 DOS。
|
|
12
|
+
利用 NumPy 广播机制进行高效计算。
|
|
13
|
+
"""
|
|
14
|
+
# E_grid shape: (N_grid,) -> 变为列向量 (N_grid, 1)
|
|
15
|
+
# eigenvalues shape: (N_eigen,) -> 变为行向量 (1, N_eigen)
|
|
16
|
+
# delta_E shape: (N_grid, N_eigen),包含了每个网格点到每个本征值的距离
|
|
17
|
+
delta_E = E_grid[:, np.newaxis] - eigenvalues[np.newaxis, :]
|
|
18
|
+
|
|
19
|
+
# 计算高斯项
|
|
20
|
+
prefactor = 1.0 / (sigma * np.sqrt(2 * np.pi))
|
|
21
|
+
gaussians = prefactor * np.exp(-0.5 * (delta_E / sigma)**2)
|
|
22
|
+
|
|
23
|
+
# 对所有本征值求和,并乘上权重
|
|
24
|
+
# axis=1 表示沿着本征值的方向求和,最终得到 (N_grid,) 的数组
|
|
25
|
+
dos_values = np.sum(gaussians * weights[np.newaxis, :], axis=1)
|
|
26
|
+
|
|
27
|
+
return dos_values
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@click.command(name='pdos')
|
|
32
|
+
@click.option('-i', type=arg_type.FileList, default='*-k*.*', help='pdos files generated by CP2K', show_default=True)
|
|
33
|
+
@click.option('--no-reference', is_flag=True, help='if specified, reference energy will not be shifted to fermi level')
|
|
34
|
+
@click.option('-o', type=str, help='output file name')
|
|
35
|
+
def main(filename, no_reference, o):
|
|
36
|
+
total_dict = {}
|
|
37
|
+
for file in filename:
|
|
38
|
+
dos = outputs.parse_pdos(file, total=True)
|
|
39
|
+
|
|
40
|
+
pdos = dos[0]
|
|
41
|
+
tdos = dos[1]
|
|
42
|
+
element = list(pdos.keys())[0]
|
|
43
|
+
data_dict = {}
|
|
44
|
+
steplen = 0.2
|
|
45
|
+
fermi = pdos[element][list(pdos[element].keys())[0]].efermi
|
|
46
|
+
gap = pdos[element][list(pdos[element].keys())[0]].get_gap()
|
|
47
|
+
sigma = 0.02
|
|
48
|
+
|
|
49
|
+
energies = pdos[element][list(pdos[element].keys())[0]].energies
|
|
50
|
+
n_min = np.floor((energies.min() - fermi) / steplen)
|
|
51
|
+
n_max = np.ceil((energies.max() - fermi) / steplen)
|
|
52
|
+
integers = np.arange(n_min, n_max + 1)
|
|
53
|
+
bins = fermi + integers * steplen
|
|
54
|
+
#e_min = energies.min() - 3 * sigma
|
|
55
|
+
#e_max = energies.max() + 3 * sigma
|
|
56
|
+
#n_step_left = np.ceil((fermi - e_min) / steplen).astype(int)
|
|
57
|
+
#n_step_right = np.ceil((e_max - fermi) / steplen).astype(int)
|
|
58
|
+
#indices = np.arange(-n_step_left, n_step_right + 1)
|
|
59
|
+
#bins = fermi + indices * steplen
|
|
60
|
+
#bins = int((energies[-1]-energies[0])/steplen)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
dos, ener = np.histogram(energies, bins=bins, weights=tdos.get_densities(Spin.up), range=(energies[0], energies[-1]))
|
|
64
|
+
#dos = calculate_gaussian_dos(bins + steplen/2, energies, tdos.get_densities(Spin.up), sigma)
|
|
65
|
+
#data_dict['energy'] = bins + steplen/2
|
|
66
|
+
data_dict['energy'] = ener[:-1] + steplen/2
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
df = pd.DataFrame(data_dict)
|
|
70
|
+
|
|
71
|
+
df['total'] = 0
|
|
72
|
+
for orb_key in pdos[element].keys():
|
|
73
|
+
data_obj = pdos[element][orb_key]
|
|
74
|
+
weights = data_obj.get_densities(Spin.up)
|
|
75
|
+
dos, ener = np.histogram(energies, bins=bins, weights=weights, range=(energies[0], energies[-1]))
|
|
76
|
+
#dos = calculate_gaussian_dos(bins + steplen/2, energies, weights, sigma)
|
|
77
|
+
df[orb_key] = dos/steplen
|
|
78
|
+
df['total'] += dos/steplen
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if Spin.down in pdos[element][list(pdos[element].keys())[0]].densities:
|
|
82
|
+
df['total_down'] = 0
|
|
83
|
+
for orb_key in pdos[element].keys():
|
|
84
|
+
data_obj = pdos[element][orb_key]
|
|
85
|
+
weights = data_obj.get_densities(Spin.down)
|
|
86
|
+
dos, ener = np.histogram(energies, bins=bins, weights=weights, range=(energies[0], energies[-1]))
|
|
87
|
+
#dos = calculate_gaussian_dos(bins + steplen/2, energies, weights, sigma)
|
|
88
|
+
df[f'{orb_key}_down'] = dos/steplen
|
|
89
|
+
df['total_down'] += dos/steplen
|
|
90
|
+
|
|
91
|
+
if o is None:
|
|
92
|
+
out_filename = f"{element}.pdos"
|
|
93
|
+
else:
|
|
94
|
+
out_filename = o
|
|
95
|
+
|
|
96
|
+
total_dict[element] = df
|
|
97
|
+
|
|
98
|
+
if not no_reference:
|
|
99
|
+
df['energy'] = df['energy'] - fermi
|
|
100
|
+
|
|
101
|
+
with open(out_filename, 'w') as f:
|
|
102
|
+
header = '#' + ' '.join([str(key) for key in df.columns]) + '\n'
|
|
103
|
+
f.write(header)
|
|
104
|
+
df.to_csv(f, sep='\t', index=False, header=False)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
energy = list(total_dict.values())[0]['energy']
|
|
108
|
+
|
|
109
|
+
total_dos = 0
|
|
110
|
+
for element, df in total_dict.items():
|
|
111
|
+
total_dos += df['total']
|
|
112
|
+
|
|
113
|
+
total_df = pd.DataFrame({'energy': energy, 'total': total_dos})
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
with open('total.pdos', 'w') as f:
|
|
117
|
+
header = '# energy total_dos' + '\n' + f'# Fermi Level: {fermi}' + f'gap: {gap}' + '\n'
|
|
118
|
+
f.write(header)
|
|
119
|
+
total_df.to_csv(f, sep='\t', index=False, header=False)
|
mdkits/md_cli/angle.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from MDAnalysis import Universe
|
|
3
|
+
from MDAnalysis.analysis.base import AnalysisBase
|
|
4
|
+
import MDAnalysis
|
|
5
|
+
import sys
|
|
6
|
+
from mdkits.util import numpy_geo, encapsulated_mda, arg_type
|
|
7
|
+
import numpy as np
|
|
8
|
+
from .setting import common_setting
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Angle_distribution(AnalysisBase):
|
|
12
|
+
def __init__(self, filename, cell, water_height, update_water, surface, distance_judg=None, angle_judg=(None, None), dt=0.001, bin_size=5):
|
|
13
|
+
u = Universe(filename)
|
|
14
|
+
u.trajectory.ts.dt = dt
|
|
15
|
+
u.dimensions = cell
|
|
16
|
+
|
|
17
|
+
self.u = u
|
|
18
|
+
self.atomgroup = u.select_atoms("all")
|
|
19
|
+
self.bin_size = bin_size
|
|
20
|
+
self.frame_count = 0
|
|
21
|
+
self.surface = surface
|
|
22
|
+
self.update_water = update_water
|
|
23
|
+
self.mid_z = u.dimensions[2]/2
|
|
24
|
+
|
|
25
|
+
self.normal_up = np.array([0, 0, 1])
|
|
26
|
+
self.normal_down = np.array([0, 0, -1])
|
|
27
|
+
self.total_angle = 180
|
|
28
|
+
|
|
29
|
+
if water_height is None:
|
|
30
|
+
sys.exit("Please specify the water height")
|
|
31
|
+
else:
|
|
32
|
+
self.water_height = water_height
|
|
33
|
+
|
|
34
|
+
if self.update_water:
|
|
35
|
+
self.distance_judg = distance_judg
|
|
36
|
+
self.angle_judg = angle_judg
|
|
37
|
+
|
|
38
|
+
if surface is not None:
|
|
39
|
+
self.surface_group = self.atomgroup.select_atoms(f"{surface}")
|
|
40
|
+
if self.surface_group.n_atoms == 0:
|
|
41
|
+
sys.exit("Please specify the correct surface group")
|
|
42
|
+
else:
|
|
43
|
+
sys.exit("Please specify a surface group")
|
|
44
|
+
|
|
45
|
+
super(Angle_distribution, self).__init__(self.atomgroup.universe.trajectory, verbose=True)
|
|
46
|
+
|
|
47
|
+
def _prepare(self):
|
|
48
|
+
self.bin_num = int(self.total_angle / self.bin_size) + 2
|
|
49
|
+
self.angle_w_distribution = np.zeros(self.bin_num, dtype=np.float64)
|
|
50
|
+
self.angle_oh_distribution = np.zeros(self.bin_num, dtype=np.float64)
|
|
51
|
+
|
|
52
|
+
def _append(self, angle, anglew=True):
|
|
53
|
+
bins = np.floor(angle / self.bin_size).astype(int) + 1
|
|
54
|
+
|
|
55
|
+
if anglew:
|
|
56
|
+
bins = bins[bins < len(self.angle_w_distribution)]
|
|
57
|
+
np.add.at(self.angle_w_distribution, bins, 1)
|
|
58
|
+
else:
|
|
59
|
+
bins = bins[bins < len(self.angle_oh_distribution)]
|
|
60
|
+
np.add.at(self.angle_oh_distribution, bins, 1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _single_frame(self):
|
|
64
|
+
surface = numpy_geo.find_surface(self.surface_group.positions[:, 2])
|
|
65
|
+
|
|
66
|
+
if surface[1] == 0:
|
|
67
|
+
o_group = self.atomgroup.select_atoms(f"name O and prop z < {surface[0]+self.water_height}", updating=True)
|
|
68
|
+
else:
|
|
69
|
+
o_group = self.atomgroup.select_atoms(f"name O and (prop z < {surface[0]+self.water_height} or prop z > {surface[1]-self.water_height})", updating=True)
|
|
70
|
+
|
|
71
|
+
h_group = self.atomgroup.select_atoms("name H")
|
|
72
|
+
|
|
73
|
+
if self.update_water:
|
|
74
|
+
o, oh1, oh2 = encapsulated_mda.update_water(self, o_group=o_group, h_group=h_group, distance_judg=self.distance_judg, angle_judg=self.angle_judg, return_index=False)
|
|
75
|
+
else:
|
|
76
|
+
o = o_group
|
|
77
|
+
oh1 = self.atomgroup[o_group.indices + 1]
|
|
78
|
+
oh2 = self.atomgroup[o_group.indices + 2]
|
|
79
|
+
|
|
80
|
+
vec1 = MDAnalysis.lib.distances.minimize_vectors(oh1.positions - o.positions, self.u.dimensions)
|
|
81
|
+
vec2 = MDAnalysis.lib.distances.minimize_vectors(oh2.positions - o.positions, self.u.dimensions)
|
|
82
|
+
|
|
83
|
+
bisector = numpy_geo.vector_between_two_vector(vec1, vec2)
|
|
84
|
+
|
|
85
|
+
angle_vec1 = np.hstack((numpy_geo.vector_vector_angle(vec1[o.positions[:, 2] < self.mid_z], self.normal_up), numpy_geo.vector_vector_angle(vec1[o.positions[:, 2] > self.mid_z], self.normal_down)))
|
|
86
|
+
|
|
87
|
+
angle_vec2 = np.hstack((numpy_geo.vector_vector_angle(vec2[o.positions[:, 2] < self.mid_z], self.normal_up), numpy_geo.vector_vector_angle(vec2[o.positions[:, 2] > self.mid_z], self.normal_down)))
|
|
88
|
+
|
|
89
|
+
angle_bisector = np.hstack((numpy_geo.vector_vector_angle(bisector[o.positions[:, 2] < self.mid_z], self.normal_up), numpy_geo.vector_vector_angle(bisector[o.positions[:, 2] > self.mid_z], self.normal_down)))
|
|
90
|
+
|
|
91
|
+
self._append(angle_vec1, anglew=False)
|
|
92
|
+
self._append(angle_vec2, anglew=False)
|
|
93
|
+
self._append(angle_bisector)
|
|
94
|
+
|
|
95
|
+
self.frame_count += 1
|
|
96
|
+
|
|
97
|
+
def _conclude(self):
|
|
98
|
+
if self.frame_count > 0:
|
|
99
|
+
average_angle_w = self.angle_w_distribution / self.frame_count
|
|
100
|
+
average_angle_oh = self.angle_oh_distribution / (self.frame_count*2)
|
|
101
|
+
bins_z = np.arange(len(average_angle_w)) * self.bin_size + self.bin_size / 2
|
|
102
|
+
conbined_data = np.column_stack((bins_z, average_angle_w, average_angle_oh))
|
|
103
|
+
np.savetxt("angle_distribution.dat", conbined_data, header="angle\tw_suf_dist\toh_suf_dist", fmt='%.5f', delimiter='\t')
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@click.command(name="angle", help="analysis angle between normal vectors and OH vector or bisector")
|
|
107
|
+
@common_setting
|
|
108
|
+
@click.option("--water_height", type=float, help="water height from surface")
|
|
109
|
+
def main(filename, cell, water_height, update_water, distance, angle, surface, r):
|
|
110
|
+
"""analysis angle between normal vectors and OH vector or bisector"""
|
|
111
|
+
a = Angle_distribution(filename, cell, water_height, update_water, distance_judg=distance, angle_judg=angle, surface=surface)
|
|
112
|
+
if r is not None:
|
|
113
|
+
if len(r) == 2:
|
|
114
|
+
a.run(start=r[0], stop=r[1])
|
|
115
|
+
elif len(r) == 3:
|
|
116
|
+
a.run(start=r[0], stop=r[1], step=r[2])
|
|
117
|
+
else:
|
|
118
|
+
a.run()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
main()
|