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.
Files changed (48) hide show
  1. mdkits/build_cli/adsorbate.py +2 -1
  2. mdkits/build_cli/build_bulk.py +2 -1
  3. mdkits/build_cli/build_interface.py +5 -0
  4. mdkits/build_cli/build_solution.py +48 -4
  5. mdkits/build_cli/build_surface.py +2 -5
  6. mdkits/build_cli/cut_surface.py +1 -1
  7. mdkits/build_cli/supercell.py +1 -1
  8. mdkits/cli/convert.py +1 -1
  9. mdkits/cli/extract.py +29 -18
  10. mdkits/{cli → dft_cli/.back}/pdos.py +1 -0
  11. mdkits/dft_cli/check_neb.py +0 -0
  12. mdkits/{cli → dft_cli}/cube.py +3 -2
  13. mdkits/dft_cli/dft_cli.py +23 -0
  14. mdkits/dft_cli/fix.py +54 -0
  15. mdkits/dft_cli/pdos.py +119 -0
  16. mdkits/md_cli/angle.py +122 -0
  17. mdkits/{cli → md_cli}/density.py +24 -19
  18. mdkits/md_cli/dipole.py +124 -0
  19. mdkits/md_cli/hb_distribution.py +185 -0
  20. mdkits/md_cli/md_cli.py +32 -0
  21. mdkits/md_cli/monitor.py +104 -0
  22. mdkits/md_cli/msd.py +44 -0
  23. mdkits/md_cli/rdf.py +53 -0
  24. mdkits/md_cli/setting.py +14 -0
  25. mdkits/md_cli/vac.py +65 -0
  26. mdkits/{cli → md_cli}/wrap.py +4 -3
  27. mdkits/mdkits.py +5 -9
  28. mdkits/util/.fig_operation.py.swp +0 -0
  29. mdkits/util/arg_type.py +18 -8
  30. mdkits/util/cp2k_input_parsing.py +5 -1
  31. mdkits/util/encapsulated_ase.py +28 -7
  32. mdkits/util/encapsulated_mda.py +4 -1
  33. mdkits/util/numpy_geo.py +10 -5
  34. mdkits/util/os_operation.py +3 -1
  35. mdkits/util/out_err.py +18 -6
  36. mdkits-1.2.3.dist-info/METADATA +370 -0
  37. mdkits-1.2.3.dist-info/RECORD +51 -0
  38. mdkits/cli/,hb_distribution_down.py +0 -114
  39. mdkits/cli/hartree_potential.py +0 -59
  40. mdkits/cli/hartree_potential_ave.py +0 -84
  41. mdkits/cli/hb.py +0 -101
  42. mdkits/cli/hb_distribution.py +0 -126
  43. mdkits/cli/packmol_input.py +0 -76
  44. mdkits-0.1.13.dist-info/METADATA +0 -226
  45. mdkits-0.1.13.dist-info/RECORD +0 -43
  46. {mdkits-0.1.13.dist-info → mdkits-1.2.3.dist-info}/LICENSE +0 -0
  47. {mdkits-0.1.13.dist-info → mdkits-1.2.3.dist-info}/WHEEL +0 -0
  48. {mdkits-0.1.13.dist-info → mdkits-1.2.3.dist-info}/entry_points.txt +0 -0
@@ -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
- print(os.path.abspath(output_filename))
48
+ out_err.path_output(output_filename)
48
49
 
49
50
 
50
51
  if __name__ == '__main__':
@@ -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
- print(os.path.abspath(o))
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('--install', is_flag=True, help="install julia and packmol")
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, install, water_number, n, tolerance, cell, gap):
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
- if install:
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
- #if args.primitive:
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
- print(os.path.abspath(out_filename))
53
+ out_err.path_output(out_filename)
57
54
 
58
55
 
59
56
  if __name__ == '__main__':
@@ -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.cell.cellpar())
73
+ out_err.cell_output(super_surface)
74
74
  out_err.path_output(o)
75
75
 
76
76
 
@@ -34,7 +34,7 @@ def main(atoms, super, cell):
34
34
 
35
35
  o = f"{atoms.filename.split('.')[-2]}_{super[0]}{super[1]}{super[2]}.cif"
36
36
  super_atom.write(o)
37
- print(os.path.abspath(o))
37
+ out_err.path_output(o)
38
38
 
39
39
 
40
40
  if __name__ == '__main__':
mdkits/cli/convert.py CHANGED
@@ -57,7 +57,7 @@ def main(atoms, c, x, d, v, coord, cp2k, center, cell):
57
57
  f.write(f"ALPHA_BETA_GAMMA {cell[3]} {cell[4]} {cell[5]}\n")
58
58
 
59
59
 
60
- print(os.path.abspath(o))
60
+ out_err.path_output(o)
61
61
 
62
62
 
63
63
  if __name__ == '__main__':
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
- with MDAnalysis.Writer(o, u.atoms.n_atoms, format='XYZ') as w:
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(u)
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'./coord/coord_{index:03d}'
30
- with MDAnalysis.Writer(o, u.atoms.n_atoms, format='XYZ') as w:
31
- w.write(u)
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
- def main(input_file_name, o, r, c):
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 not os.path.exists('./coord'):
67
- os.makedirs('./coord')
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('./coord')
71
- os.makedirs('./coord')
72
- write_to_xyz_s(u, frames, cut=cut)
73
- click.echo(os.path.abspath('./coord'))
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
- write_to_xyz(u, frames, o, cut=cut)
76
- click.echo(os.path.abspath(o))
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
@@ -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()