valyte 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.
valyte/Logo.png ADDED
Binary file
valyte/__init__.py ADDED
File without changes
valyte/band.py ADDED
@@ -0,0 +1,56 @@
1
+ """
2
+ Band structure KPOINTS generation module for Valyte.
3
+ """
4
+
5
+ import os
6
+ from pymatgen.core import Structure
7
+ from pymatgen.symmetry.bandstructure import HighSymmKpath
8
+
9
+
10
+ def generate_band_kpoints(poscar_path="POSCAR", npoints=40, output="KPOINTS"):
11
+ """
12
+ Generates KPOINTS file in line-mode for band structure calculations.
13
+ Uses SeeK-path method for high-symmetry path determination.
14
+
15
+ Args:
16
+ poscar_path (str): Path to input POSCAR file.
17
+ npoints (int): Number of points per segment (default: 40).
18
+ output (str): Output filename for KPOINTS.
19
+ """
20
+
21
+ if not os.path.exists(poscar_path):
22
+ raise FileNotFoundError(f"{poscar_path} not found")
23
+
24
+ # Read structure
25
+ structure = Structure.from_file(poscar_path)
26
+
27
+ # Get high-symmetry path using SeeK-path method
28
+ kpath = HighSymmKpath(structure, path_type="setyawan_curtarolo")
29
+
30
+ # Get the path
31
+ path = kpath.kpath["path"]
32
+ kpoints = kpath.kpath["kpoints"]
33
+
34
+ # Write KPOINTS file
35
+ with open(output, 'w') as f:
36
+ f.write("k-points for band structure\n")
37
+ f.write(f"{npoints}\n")
38
+ f.write("Line-mode\n")
39
+ f.write("Reciprocal\n")
40
+
41
+ # Write each segment
42
+ for segment in path:
43
+ for i in range(len(segment) - 1):
44
+ start = segment[i]
45
+ end = segment[i + 1]
46
+
47
+ start_coords = kpoints[start]
48
+ end_coords = kpoints[end]
49
+
50
+ f.write(f" {start_coords[0]:.6f} {start_coords[1]:.6f} {start_coords[2]:.6f} ! {start}\n")
51
+ f.write(f" {end_coords[0]:.6f} {end_coords[1]:.6f} {end_coords[2]:.6f} ! {end}\n")
52
+ f.write("\n")
53
+
54
+ # Print success message
55
+ path_str = ' → '.join([' - '.join(seg) for seg in path])
56
+ print(f"✅ KPOINTS generated: {output} ({path_str}, {npoints} pts/seg)")
valyte/band_plot.py ADDED
@@ -0,0 +1,127 @@
1
+ import os
2
+ import numpy as np
3
+ import matplotlib as mpl
4
+ mpl.use("agg")
5
+ mpl.rcParams["axes.unicode_minus"] = False
6
+ import matplotlib.pyplot as plt
7
+ from pymatgen.io.vasp import Vasprun, BSVasprun
8
+ from pymatgen.electronic_structure.plotter import BSPlotter
9
+
10
+ def plot_band_structure(vasprun_path, kpoints_path=None, output="valyte_band.png",
11
+ ylim=None, figsize=(4, 4), dpi=400, font="Arial"):
12
+ """
13
+ Plots the electronic band structure from vasprun.xml.
14
+
15
+ Args:
16
+ vasprun_path (str): Path to vasprun.xml file.
17
+ kpoints_path (str, optional): Path to KPOINTS file (for labels).
18
+ output (str): Output filename.
19
+ ylim (tuple, optional): Energy range (min, max).
20
+ figsize (tuple): Figure size in inches.
21
+ dpi (int): Resolution of the output image.
22
+ font (str): Font family.
23
+ """
24
+
25
+ # --- Font configuration ---
26
+ font_map = {
27
+ "arial": "Arial",
28
+ "helvetica": "Helvetica",
29
+ "times": "Times New Roman",
30
+ "times new roman": "Times New Roman",
31
+ }
32
+ font = font_map.get(font.lower(), "Arial")
33
+ mpl.rcParams["font.family"] = font
34
+ mpl.rcParams["axes.linewidth"] = 1.4
35
+ mpl.rcParams["font.weight"] = "bold"
36
+ mpl.rcParams["font.size"] = 14
37
+ mpl.rcParams["xtick.major.width"] = 1.2
38
+ mpl.rcParams["ytick.major.width"] = 1.2
39
+
40
+ # print(f"🔍 Reading {vasprun_path} ...") # Silent mode
41
+
42
+ try:
43
+ # Load VASP output
44
+ # BSVasprun is optimized for band structures
45
+ vr = BSVasprun(vasprun_path, parse_projected_eigen=False)
46
+ bs = vr.get_band_structure(kpoints_filename=kpoints_path, line_mode=True)
47
+ except Exception as e:
48
+ raise ValueError(f"Failed to load band structure: {e}")
49
+
50
+ # Use BSPlotter to get the data in a plot-friendly format
51
+ bs_plotter = BSPlotter(bs)
52
+ data = bs_plotter.bs_plot_data(zero_to_efermi=True)
53
+
54
+ # Extract data
55
+ distances = data['distances'] # List of lists (one per segment)
56
+ energies = data['energy'] # Can be list of dicts OR dict of lists depending on pymatgen version/structure
57
+ ticks = data['ticks'] # Dict with 'distance' and 'label'
58
+
59
+ # Setup plot
60
+ fig, ax = plt.subplots(figsize=figsize)
61
+
62
+ # Colors
63
+ color_vb = "#8e44ad" # Purple
64
+ color_cb = "#2a9d8f" # Teal
65
+
66
+ # Plot bands
67
+ # Iterate over segments
68
+ for i in range(len(distances)):
69
+ d = distances[i]
70
+
71
+ # Handle different energy data structures
72
+ if isinstance(energies, dict):
73
+ # Structure: {'1': [seg1, seg2, ...], '-1': ...}
74
+ # Iterate over spins
75
+ for spin in energies:
76
+ # energies[spin] is a list of segments
77
+ # energies[spin][i] is the list of bands for segment i
78
+ for band in energies[spin][i]:
79
+ # Determine color based on energy relative to VBM (0 eV)
80
+ if np.mean(band) <= 0:
81
+ c = color_vb
82
+ else:
83
+ c = color_cb
84
+ ax.plot(d, band, color=c, lw=1.5, alpha=1.0)
85
+ else:
86
+ # Structure: [{'1': bands, ...}, {'1': bands, ...}] (List of dicts)
87
+ # Iterate over spin channels in this segment
88
+ for spin in energies[i]:
89
+ # energies[i][spin] is a list of arrays (one per band)
90
+ for band in energies[i][spin]:
91
+ if np.mean(band) <= 0:
92
+ c = color_vb
93
+ else:
94
+ c = color_cb
95
+ ax.plot(d, band, color=c, lw=1.5, alpha=1.0)
96
+
97
+ # Setup X-axis (K-path)
98
+ ax.set_xticks(ticks['distance'])
99
+ # Clean up labels (remove formatting like $ if needed, but pymatgen usually does a good job)
100
+ clean_labels = [l.replace("$\\mid$", "|") for l in ticks['label']]
101
+ ax.set_xticklabels(clean_labels, fontsize=14, fontweight="bold")
102
+
103
+ # Draw vertical lines at high-symmetry points
104
+ for d in ticks['distance']:
105
+ ax.axvline(d, color="k", lw=0.8, ls="-", alpha=0.3)
106
+
107
+ # Draw VBM line (E=0)
108
+ ax.axhline(0, color="k", lw=0.8, ls="--", alpha=0.5)
109
+
110
+ # Setup Y-axis
111
+ ax.set_ylabel("Energy (eV)", fontsize=16, fontweight="bold", labelpad=8)
112
+ if ylim:
113
+ ax.set_ylim(ylim)
114
+ # Set y-ticks with 1 eV spacing
115
+ yticks = np.arange(np.ceil(ylim[0]), np.floor(ylim[1]) + 1, 1)
116
+ ax.set_yticks(yticks)
117
+ else:
118
+ # Default zoom around gap
119
+ ax.set_ylim(-4, 4)
120
+ ax.set_yticks(np.arange(-4, 5, 1))
121
+
122
+ ax.set_xlim(distances[0][0], distances[-1][-1])
123
+
124
+ plt.tight_layout()
125
+ plt.savefig(output, dpi=dpi)
126
+ plt.close(fig)
127
+ # print(f"✅ Band structure saved to {output}") # Silent mode
valyte/cli.py ADDED
@@ -0,0 +1,217 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Valyte CLI Tool
4
+ ===============
5
+
6
+ A post-processing tool for VASP outputs, designed to create publication-quality
7
+ plots with a modern aesthetic. Supports DOS and band structure plotting.
8
+
9
+ Features:
10
+ - DOS plotting with gradient fills
11
+ - Band structure plotting
12
+ - Smart legend positioning
13
+ - Custom font support
14
+ """
15
+
16
+ import os
17
+ import sys
18
+ import argparse
19
+ import re
20
+ import warnings
21
+
22
+ # Suppress pymatgen warnings
23
+ warnings.filterwarnings("ignore", category=UserWarning, module="pymatgen")
24
+
25
+ from valyte.supercell import create_supercell
26
+ from valyte.band import generate_band_kpoints
27
+ from valyte.band_plot import plot_band_structure
28
+ from valyte.dos_plot import load_dos, plot_dos
29
+ from valyte.kpoints import generate_kpoints_interactive
30
+
31
+ def parse_element_selection(inputs):
32
+ """
33
+ Parses user input for elements and orbitals.
34
+
35
+ Args:
36
+ inputs (list): List of strings, e.g., ["Ag", "Bi(s)", "O(p)"]
37
+
38
+ Returns:
39
+ tuple: (elements_to_load, plotting_config)
40
+ elements_to_load (list): Elements to extract from VASP data.
41
+ plotting_config (list): List of (Element, Orbital) tuples.
42
+ """
43
+ if not inputs:
44
+ return None, None
45
+
46
+ elements_to_load = set()
47
+ plotting_config = []
48
+
49
+ # Regex to match "Element" or "Element(orbital)"
50
+ pattern = re.compile(r"^([A-Za-z]+)(?:\(([spdf])\))?$")
51
+
52
+ for item in inputs:
53
+ match = pattern.match(item)
54
+ if match:
55
+ el = match.group(1)
56
+ orb = match.group(2) # None if no orbital specified
57
+
58
+ elements_to_load.add(el)
59
+ if orb:
60
+ plotting_config.append((el, orb))
61
+ else:
62
+ plotting_config.append((el, 'total'))
63
+ else:
64
+ print(f"⚠️ Warning: Could not parse '{item}'. Ignoring.")
65
+
66
+
67
+ return list(elements_to_load), plotting_config
68
+
69
+
70
+ # ===============================================================
71
+ # Main CLI
72
+ # ===============================================================
73
+ def main():
74
+ """
75
+ Main entry point for the CLI.
76
+ """
77
+ parser = argparse.ArgumentParser(description="Valyte: VASP Post-Processing Tool")
78
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
79
+
80
+ # --- DOS Subcommand ---
81
+ dos_parser = subparsers.add_parser("dos", help="Plot Density of States (DOS)")
82
+ dos_parser.add_argument("filepath", nargs="?", help="Path to vasprun.xml or directory containing it (optional)")
83
+ dos_parser.add_argument("--vasprun", help="Explicit path to vasprun.xml (alternative to positional argument)")
84
+ dos_parser.add_argument("-e", "--elements", nargs="+", help="Elements/Orbitals to plot (e.g., 'Fe O' or 'Fe(d) O(p)')")
85
+ dos_parser.add_argument("-o", "--output", default="valyte_dos.png", help="Output filename")
86
+ dos_parser.add_argument("--xlim", nargs=2, type=float, default=[-6, 6], help="Energy range (min max)")
87
+ dos_parser.add_argument("--ylim", nargs=2, type=float, help="DOS range (min max)")
88
+ dos_parser.add_argument("--scale", type=float, default=1.0, help="Scaling factor for Y-axis (zoom in)")
89
+ dos_parser.add_argument("--fermi", action="store_true", help="Draw dashed line at Fermi level (E=0)")
90
+ dos_parser.add_argument("--pdos", action="store_true", help="Plot only Projected DOS (hide Total DOS)")
91
+ dos_parser.add_argument("--legend-cutoff", type=float, default=0.10, help="Threshold for legend visibility (0.0-1.0)")
92
+ dos_parser.add_argument("--font", default="Arial", help="Font family")
93
+
94
+ # --- Supercell Subcommand ---
95
+ supercell_parser = subparsers.add_parser("supercell", help="Create a supercell")
96
+ supercell_parser.add_argument("nx", type=int, help="Supercell size x")
97
+ supercell_parser.add_argument("ny", type=int, help="Supercell size y")
98
+ supercell_parser.add_argument("nz", type=int, help="Supercell size z")
99
+ supercell_parser.add_argument("-i", "--input", default="POSCAR", help="Input POSCAR file")
100
+ supercell_parser.add_argument("-o", "--output", default="POSCAR_supercell", help="Output filename")
101
+
102
+ # --- Band Structure Subcommand ---
103
+ band_parser = subparsers.add_parser("band", help="Band structure utilities")
104
+ band_subparsers = band_parser.add_subparsers(dest="band_command", help="Band commands")
105
+
106
+ # Band Plotting (default if no subcommand)
107
+ # Note: argparse doesn't easily support default subcommands, so we handle this in logic
108
+ band_parser.add_argument("--vasprun", default=".", help="Path to vasprun.xml or directory")
109
+ band_parser.add_argument("--kpoints", help="Path to KPOINTS file (for labels)")
110
+ band_parser.add_argument("-o", "--output", default="valyte_band.png", help="Output filename")
111
+ band_parser.add_argument("--ylim", nargs=2, type=float, help="Energy range (min max)")
112
+ band_parser.add_argument("--font", default="Arial", help="Font family")
113
+
114
+ # Band KPOINTS Generation
115
+ kpt_gen_parser = band_subparsers.add_parser("kpt-gen", help="Generate KPOINTS for band structure")
116
+ kpt_gen_parser.add_argument("-i", "--input", default="POSCAR", help="Input POSCAR file")
117
+ kpt_gen_parser.add_argument("-n", "--npoints", type=int, default=40, help="Points per segment")
118
+ kpt_gen_parser.add_argument("-o", "--output", default="KPOINTS", help="Output filename")
119
+
120
+ # --- K-Point Generation (Interactive) ---
121
+ subparsers.add_parser("kpt", help="Interactive K-Point Generation (SCF)")
122
+
123
+ args = parser.parse_args()
124
+
125
+ if args.command == "dos":
126
+ # Resolve filepath: positional > flag > current dir
127
+ target_path = args.filepath if args.filepath else args.vasprun
128
+ if not target_path:
129
+ target_path = "."
130
+
131
+ elements, plotting_config = parse_element_selection(args.elements)
132
+
133
+ try:
134
+ dos_data, pdos_data = load_dos(target_path, elements)
135
+ plot_dos(
136
+ dos_data, pdos_data,
137
+ out=args.output,
138
+ xlim=tuple(args.xlim),
139
+ ylim=tuple(args.ylim) if args.ylim else None,
140
+ font=args.font,
141
+ show_fermi=args.fermi,
142
+ show_total=not args.pdos,
143
+ plotting_config=plotting_config,
144
+ legend_cutoff=args.legend_cutoff,
145
+ scale_factor=args.scale
146
+ )
147
+ # print(f"✅ DOS plot saved to {args.output}") # Silent mode
148
+ except Exception as e:
149
+ print(f"❌ Error: {e}")
150
+ sys.exit(1)
151
+
152
+ elif args.command == "supercell":
153
+ try:
154
+ create_supercell(
155
+ poscar_path=args.input,
156
+ nx=args.nx,
157
+ ny=args.ny,
158
+ nz=args.nz,
159
+ output=args.output
160
+ )
161
+ except Exception as e:
162
+ print(f"❌ Error: {e}")
163
+ sys.exit(1)
164
+
165
+ elif args.command == "kpt":
166
+ try:
167
+ generate_kpoints_interactive()
168
+ except Exception as e:
169
+ print(f"❌ Error: {e}")
170
+ sys.exit(1)
171
+
172
+ elif args.command == "band":
173
+ if args.band_command == "kpt-gen":
174
+ try:
175
+ generate_band_kpoints(
176
+ poscar_path=args.input,
177
+ npoints=args.npoints,
178
+ output=args.output
179
+ )
180
+ except Exception as e:
181
+ print(f"❌ Error: {e}")
182
+ sys.exit(1)
183
+ elif args.band_command == "plot" or args.band_command is None:
184
+ # Default behavior for 'valyte band' is plotting
185
+ try:
186
+ # Determine input path: --vasprun > positional > current dir
187
+ target_path = args.vasprun or args.filepath or "."
188
+ if os.path.isdir(target_path):
189
+ target_path = os.path.join(target_path, "vasprun.xml")
190
+
191
+ # Determine KPOINTS path
192
+ kpoints_path = args.kpoints
193
+ if not kpoints_path:
194
+ # Try to find KPOINTS in the same directory as vasprun.xml
195
+ base_dir = os.path.dirname(target_path)
196
+ potential_kpoints = os.path.join(base_dir, "KPOINTS")
197
+ if os.path.exists(potential_kpoints):
198
+ kpoints_path = potential_kpoints
199
+
200
+ plot_band_structure(
201
+ vasprun_path=target_path,
202
+ kpoints_path=kpoints_path,
203
+ output=args.output,
204
+ ylim=tuple(args.ylim) if args.ylim else None,
205
+ font=args.font
206
+ )
207
+ except Exception:
208
+ import traceback
209
+ traceback.print_exc()
210
+ sys.exit(1)
211
+ else:
212
+ band_parser.print_help()
213
+ else:
214
+ parser.print_help()
215
+
216
+ if __name__ == "__main__":
217
+ main()
valyte/dos_plot.py ADDED
@@ -0,0 +1,514 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ DOS Plotting Module
4
+ ===================
5
+
6
+ Handles Density of States (DOS) plotting with gradient fills and smart legend.
7
+ """
8
+
9
+ import os
10
+ import numpy as np
11
+ import matplotlib as mpl
12
+ mpl.use("agg")
13
+ mpl.rcParams["axes.unicode_minus"] = False
14
+ import matplotlib.pyplot as plt
15
+ import matplotlib.colors as mcolors
16
+ from matplotlib.patches import Polygon
17
+ from matplotlib.ticker import AutoMinorLocator
18
+ from pymatgen.io.vasp import Vasprun
19
+ from pymatgen.electronic_structure.core import Spin
20
+
21
+
22
+ # ===============================================================
23
+ # Gradient fill aesthetic
24
+ # ===============================================================
25
+ def gradient_fill(x, y, ax=None, color=None, xlim=None, **kwargs):
26
+ """
27
+ Fills the area under a curve with a vertical gradient.
28
+
29
+ Args:
30
+ x (array-like): X-axis data (Energy).
31
+ y (array-like): Y-axis data (DOS).
32
+ ax (matplotlib.axes.Axes, optional): The axes to plot on. Defaults to current axes.
33
+ color (str, optional): The base color for the gradient.
34
+ xlim (tuple, optional): X-axis limits to restrict gradient fill.
35
+ **kwargs: Additional arguments passed to ax.plot.
36
+
37
+ Returns:
38
+ matplotlib.lines.Line2D: The line object representing the curve.
39
+ """
40
+ if ax is None:
41
+ ax = plt.gca()
42
+
43
+ # Don't filter by xlim - use full data range for better appearance
44
+ if len(x) == 0 or len(y) == 0:
45
+ return None
46
+
47
+ # Plot the main line
48
+ line, = ax.plot(x, y, color=color, lw=2, **kwargs)
49
+
50
+ # Determine fill color and alpha
51
+ fill_color = line.get_color() if color is None else color
52
+ alpha = line.get_alpha() or 1.0
53
+ zorder = line.get_zorder()
54
+
55
+ # Create a gradient image with more aggressive alpha
56
+ z = np.empty((100, 1, 4))
57
+ rgb = mcolors.to_rgb(fill_color)
58
+ z[:, :, :3] = rgb
59
+
60
+
61
+ # Gradient: transparent at bottom (y=0), opaque near the curve
62
+ # This creates a gradient from bottom to top of the filled area
63
+ alpha_gradient = np.linspace(0.05, 0.75, 100)
64
+
65
+ # If data is negative (Spin Down), we want opaque at bottom (peak) and transparent at top (axis)
66
+ # Current extent is [ymin, ymax]. ymin is bottom. ymax is top (0).
67
+ # So we want High Alpha at index 0 and Low Alpha at index 100.
68
+ if np.mean(y) < 0:
69
+ alpha_gradient = alpha_gradient[::-1]
70
+
71
+ z[:, :, -1] = alpha_gradient[:, None]
72
+
73
+ xmin, xmax = x.min(), x.max()
74
+ ymin, ymax = min(y.min(), 0), max(y.max(), 0)
75
+
76
+ # Handle pure negative or pure positive cases to avoid singular extent
77
+ if ymax == ymin:
78
+ ymax += 1e-6
79
+
80
+ # Display the gradient image
81
+ im = ax.imshow(z, aspect="auto", extent=[xmin, xmax, ymin, ymax],
82
+ origin="lower", zorder=zorder)
83
+
84
+ # Clip the gradient to the area under the curve
85
+ # We need to close the polygon at y=0
86
+ xy = np.column_stack([x, y])
87
+
88
+ # Construct polygon vertices:
89
+ # Start at (xmin, 0), go along curve (x, y), end at (xmax, 0), close back to start
90
+ verts = np.vstack([[x[0], 0], xy, [x[-1], 0], [x[0], 0]])
91
+
92
+ clip = Polygon(verts, lw=0, facecolor="none", closed=True)
93
+ ax.add_patch(clip)
94
+ im.set_clip_path(clip)
95
+
96
+ return line
97
+
98
+
99
+ # ===============================================================
100
+ # Data container
101
+ # ===============================================================
102
+ class ValyteDos:
103
+ """
104
+ Container for Total DOS data.
105
+
106
+ Attributes:
107
+ energies (np.ndarray): Array of energy values (shifted by Fermi energy).
108
+ densities (dict): Dictionary of {Spin: np.ndarray} for total DOS.
109
+ efermi (float): Fermi energy.
110
+ """
111
+ def __init__(self, energies, densities, efermi):
112
+ self.energies = np.array(energies)
113
+ self.densities = densities
114
+ self.efermi = float(efermi)
115
+
116
+ @property
117
+ def total(self):
118
+ """Returns the sum of all spin channels."""
119
+ tot = np.zeros_like(self.energies)
120
+ for spin in self.densities:
121
+ tot += self.densities[spin]
122
+ return tot
123
+
124
+ @property
125
+ def spin_up(self):
126
+ return self.densities.get(Spin.up, np.zeros_like(self.energies))
127
+
128
+ @property
129
+ def spin_down(self):
130
+ return self.densities.get(Spin.down, np.zeros_like(self.energies))
131
+
132
+
133
+ # ===============================================================
134
+ # Load DOS
135
+ # ===============================================================
136
+ def load_dos(vasprun, elements=None, **_):
137
+ """
138
+ Loads DOS data from a vasprun.xml file using pymatgen.
139
+
140
+ Args:
141
+ vasprun (str): Path to the vasprun.xml file or directory containing it.
142
+ elements (list or dict, optional): Specific elements to extract PDOS for.
143
+
144
+ Returns:
145
+ tuple: (ValyteDos object, dict of PDOS data)
146
+ """
147
+
148
+ # Handle directory input
149
+ if os.path.isdir(vasprun):
150
+ vasprun = os.path.join(vasprun, "vasprun.xml")
151
+
152
+ if not os.path.exists(vasprun):
153
+ raise FileNotFoundError(f"{vasprun} not found")
154
+
155
+ # Parse VASP output
156
+ vr = Vasprun(vasprun)
157
+ dos = vr.complete_dos
158
+
159
+ # Get Fermi Energy
160
+ efermi = dos.efermi
161
+
162
+ # Attempt to align VBM to 0 for insulators/semiconductors
163
+ try:
164
+ # Try using BandStructure first (more robust)
165
+ bs = vr.get_band_structure()
166
+ if not bs.is_metal():
167
+ efermi = bs.get_vbm()["energy"]
168
+ except Exception:
169
+ # Fallback to DOS-based detection
170
+ try:
171
+ cbm, vbm = dos.get_cbm_vbm()
172
+ if cbm - vbm > 0.01: # Band gap detected
173
+ efermi = vbm
174
+ except Exception:
175
+ pass
176
+
177
+ # Shift energies to set reference at 0
178
+ energies = dos.energies - efermi
179
+
180
+ # Extract Projected DOS
181
+ pdos = get_pdos(dos, elements)
182
+
183
+ return ValyteDos(energies, dos.densities, efermi), pdos
184
+
185
+
186
+ # ===============================================================
187
+ # Extract PDOS
188
+ # ===============================================================
189
+ def get_pdos(dos, elements=None):
190
+ """
191
+ Extracts Projected DOS (PDOS) for specified elements.
192
+
193
+ Args:
194
+ dos (pymatgen.electronic_structure.dos.CompleteDos): The complete DOS object.
195
+ elements (list or dict, optional): Elements to extract. If None, extracts all.
196
+
197
+ Returns:
198
+ dict: A dictionary where keys are element symbols and values are dicts of orbital DOS.
199
+ pdos[element][orbital] = {Spin.up: array, Spin.down: array}
200
+ """
201
+ structure = dos.structure
202
+ symbols = [str(site.specie) for site in structure]
203
+
204
+ # If no elements specified, use all unique elements in the structure
205
+ if not elements:
206
+ unique = sorted(set(symbols))
207
+ elements = {el: () for el in unique}
208
+ else:
209
+ # Ensure elements is a dict if passed as list
210
+ if isinstance(elements, list):
211
+ elements = {el: () for el in elements}
212
+
213
+ pdos = {}
214
+ for el in elements:
215
+ # Find all sites corresponding to this element
216
+ el_sites = [s for s in structure if str(s.specie) == el]
217
+ el_pdos = {}
218
+
219
+ for site in el_sites:
220
+ try:
221
+ site_dos = dos.get_site_spd_dos(site)
222
+ except Exception:
223
+ continue
224
+
225
+ # Sum up contributions from orbitals (s, p, d, f)
226
+ for orb, orb_dos in site_dos.items():
227
+ label = orb.name[0] # e.g., 's', 'p', 'd'
228
+
229
+ # Initialize dictionaries for spins if not exists
230
+ if label not in el_pdos:
231
+ el_pdos[label] = {}
232
+
233
+ for spin in orb_dos.densities:
234
+ if spin not in el_pdos[label]:
235
+ el_pdos[label][spin] = np.zeros_like(dos.energies)
236
+ el_pdos[label][spin] += orb_dos.densities[spin]
237
+
238
+ pdos[el] = el_pdos
239
+ return pdos
240
+
241
+
242
+ # ===============================================================
243
+ # Plotting with smart legend & font control (Valyte theme)
244
+ # ===============================================================
245
+ def plot_dos(dos, pdos, out="valyte_dos.png",
246
+ xlim=(-6, 6), ylim=None, figsize=(5, 4),
247
+ dpi=400, legend_loc="auto", font="Arial",
248
+ show_fermi=False, show_total=True, plotting_config=None,
249
+ legend_cutoff=0.10, scale_factor=1.0):
250
+ """
251
+ Plots the Total and Projected DOS with the Valyte visual style.
252
+
253
+ Args:
254
+ dos (ValyteDos): The total DOS data.
255
+ pdos (dict): The projected DOS data.
256
+ out (str): Output filename.
257
+ xlim (tuple): Energy range (min, max).
258
+ ylim (tuple, optional): DOS range (min, max).
259
+ figsize (tuple): Figure size in inches.
260
+ dpi (int): Resolution of the output image.
261
+ legend_loc (str): Legend location strategy.
262
+ font (str): Font family to use.
263
+ show_fermi (bool): Whether to draw a dashed line at the Fermi level (E=0).
264
+ show_total (bool): Whether to plot the Total DOS.
265
+ plotting_config (list): List of (Element, Orbital) tuples to plot.
266
+ legend_cutoff (float): Threshold (as fraction) for showing items in legend (default: 0.10).
267
+ scale_factor (float): Factor to scale the Y-axis limits (zoom in).
268
+ """
269
+
270
+ # --- Font configuration ---
271
+ font_map = {
272
+ "arial": "Arial",
273
+ "helvetica": "Helvetica",
274
+ "times": "Times New Roman",
275
+ "times new roman": "Times New Roman",
276
+ }
277
+ font = font_map.get(font.lower(), "Arial")
278
+ mpl.rcParams["font.family"] = font
279
+ mpl.rcParams["axes.linewidth"] = 1.4
280
+ mpl.rcParams["font.weight"] = "bold"
281
+ mpl.rcParams["font.size"] = 12
282
+
283
+ plt.style.use("default")
284
+ fig, ax = plt.subplots(figsize=figsize)
285
+
286
+ # Check if spin-polarized
287
+ is_spin_polarized = Spin.down in dos.densities
288
+
289
+ # Fermi level line (optional)
290
+ if show_fermi:
291
+ ax.axvline(0, color="k", lw=0.8, ls="--", alpha=0.7)
292
+
293
+ # Zero line for spin polarized plots
294
+ if is_spin_polarized:
295
+ ax.axhline(0, color="k", lw=0.5, alpha=1.0)
296
+
297
+ # Color palette for elements
298
+ # Expanded color palette for better distinction
299
+ # Reordered to maximize contrast between consecutive items
300
+ palette = [
301
+ "#4b0082", # Indigo
302
+ "#e63946", # Red
303
+ "#2a9d8f", # Teal
304
+ "#ffb703", # Yellow
305
+ "#0077b6", # Blue
306
+ "#8e44ad", # Purple
307
+ "#d62828", # Dark Red
308
+ "#118ab2", # Light Blue
309
+ "#f4a261", # Orange
310
+ "#003049", # Dark Blue
311
+ "#6a994e", # Green
312
+ "#023e8a", # Royal Blue
313
+ "#0096c7", # Cyan
314
+ "#00b4d8", # Sky Blue
315
+ "#48cae4", # Light Cyan
316
+ "#90e0ef", # Pale Blue
317
+ "#ade8f4", # Very Pale Blue
318
+ "#caf0f8" # White Blue
319
+ ]
320
+ lines, labels = [], []
321
+
322
+ # Determine mask for visible x-range (used for scaling and legend)
323
+ x_mask = (dos.energies >= xlim[0]) & (dos.energies <= xlim[1])
324
+
325
+ # Determine what to plot
326
+ if plotting_config:
327
+ items_to_plot = plotting_config
328
+ else:
329
+ # Default: Plot all orbitals for each loaded element
330
+ items_to_plot = []
331
+ for el, el_pdos in pdos.items():
332
+ for orb in el_pdos.keys():
333
+ items_to_plot.append((el, orb))
334
+
335
+ # Plot PDOS
336
+ max_visible_y = 0 # Track maximum Y value in visible range
337
+ min_visible_y = 0 # Track minimum Y value (for spin down)
338
+
339
+ for i, (el, orb) in enumerate(items_to_plot):
340
+ if el not in pdos:
341
+ continue
342
+
343
+ # Assign unique color for each orbital contribution
344
+ c = palette[i % len(palette)]
345
+
346
+ # Prepare data for plotting
347
+ # y_data_up and y_data_down
348
+
349
+ if orb == 'total':
350
+ # Sum all orbitals for this element
351
+ y_up = np.zeros_like(dos.energies)
352
+ y_down = np.zeros_like(dos.energies)
353
+
354
+ for o_data in pdos[el].values():
355
+ y_up += o_data.get(Spin.up, np.zeros_like(dos.energies))
356
+ y_down += o_data.get(Spin.down, np.zeros_like(dos.energies))
357
+
358
+ label = el
359
+ else:
360
+ if orb in pdos[el]:
361
+ y_up = pdos[el][orb].get(Spin.up, np.zeros_like(dos.energies))
362
+ y_down = pdos[el][orb].get(Spin.down, np.zeros_like(dos.energies))
363
+ label = f"{el}({orb})"
364
+ else:
365
+ continue
366
+
367
+ # Invert spin down
368
+ y_down = -y_down
369
+
370
+ # Check contribution in visible range
371
+ visible_y_up = y_up[x_mask]
372
+ visible_y_down = y_down[x_mask]
373
+
374
+ has_visible_data = False
375
+ current_max_y = 0
376
+
377
+ if len(visible_y_up) > 0:
378
+ max_y = np.max(visible_y_up)
379
+ max_visible_y = max(max_visible_y, max_y)
380
+ current_max_y = max(current_max_y, max_y)
381
+ if max_y > 1e-6: has_visible_data = True
382
+
383
+ if is_spin_polarized and len(visible_y_down) > 0:
384
+ min_y = np.min(visible_y_down)
385
+ min_visible_y = min(min_visible_y, min_y)
386
+ current_max_y = max(current_max_y, abs(min_y))
387
+ if abs(min_y) > 1e-6: has_visible_data = True
388
+
389
+ # Store for later threshold check and plotting
390
+ # We plot a dummy line for the legend
391
+ line, = ax.plot(dos.energies, y_up, lw=1.5, color=c, label=label, alpha=0)
392
+ lines.append({
393
+ 'line': line,
394
+ 'y_up': y_up,
395
+ 'y_down': y_down,
396
+ 'max_y': current_max_y,
397
+ 'color': c,
398
+ 'label': label
399
+ })
400
+
401
+ # Calculate threshold (legend_cutoff of max visible)
402
+ # Use the overall max absolute value found
403
+ global_max = max(max_visible_y, abs(min_visible_y))
404
+ threshold = legend_cutoff * global_max
405
+
406
+ # Filter legend items but keep all plot lines
407
+ final_lines = []
408
+ final_labels = []
409
+
410
+ for item in lines:
411
+ line = item['line']
412
+ y_up = item['y_up']
413
+ y_down = item['y_down']
414
+ c = item['color']
415
+ label = item['label']
416
+ max_y = item['max_y']
417
+
418
+ # Always plot the line (make it visible)
419
+ line.set_alpha(1.0)
420
+
421
+ # Apply gradient fill for visible lines
422
+ # Spin Up
423
+ gradient_fill(dos.energies, y_up, ax=ax, color=c, alpha=0.9)
424
+
425
+ # Spin Down
426
+ if is_spin_polarized:
427
+ gradient_fill(dos.energies, y_down, ax=ax, color=c, alpha=0.9)
428
+
429
+ # Only add to legend if above main threshold
430
+ if max_y >= threshold:
431
+ final_lines.append(line)
432
+ final_labels.append(label)
433
+
434
+ # Update lines and labels for legend creation
435
+ lines = final_lines
436
+ labels = final_labels
437
+
438
+ # Plot Total DOS
439
+ if show_total:
440
+ y_total_up = dos.spin_up
441
+ y_total_down = -dos.spin_down
442
+
443
+ ax.plot(dos.energies, y_total_up, color="k", lw=1.2, label="Total DOS")
444
+ gradient_fill(dos.energies, y_total_up, ax=ax, color="k", alpha=0.15)
445
+
446
+ if is_spin_polarized:
447
+ ax.plot(dos.energies, y_total_down, color="k", lw=1.2)
448
+ gradient_fill(dos.energies, y_total_down, ax=ax, color="k", alpha=0.15)
449
+
450
+ # Update max/min range for auto-scaling
451
+ visible_total_up = y_total_up[x_mask]
452
+ visible_total_down = y_total_down[x_mask]
453
+ if len(visible_total_up) > 0:
454
+ max_visible_y = max(max_visible_y, np.max(visible_total_up))
455
+ if len(visible_total_down) > 0:
456
+ min_visible_y = min(min_visible_y, np.min(visible_total_down))
457
+
458
+ # Auto-scale Y-axis based on visible range if ylim not provided
459
+ if not ylim:
460
+ if max_visible_y > 0 or min_visible_y < 0:
461
+ # Apply scaling factor to the limit (zoom in)
462
+ upper_limit = (max_visible_y * 1.1) / scale_factor
463
+ lower_limit = (min_visible_y * 1.1) / scale_factor if is_spin_polarized else 0
464
+
465
+ # If spin polarized, maybe make symmetric if user wants?
466
+ # For now, let's just use the data range.
467
+ # But often symmetric is nicer. Let's stick to data range for now.
468
+
469
+ ax.set_ylim(lower_limit, upper_limit)
470
+ else:
471
+ ax.set_ylim(*ylim)
472
+
473
+ # Axis settings
474
+ ax.set_xlim(*xlim)
475
+ ax.set_xlabel("Energy (eV)", fontsize=14, weight="bold", labelpad=6)
476
+ ax.set_ylabel("Density of States", fontsize=14, weight="bold", labelpad=6)
477
+
478
+ # Set x-ticks with 1 eV spacing
479
+ xticks = np.arange(np.ceil(xlim[0]), np.floor(xlim[1]) + 1, 1)
480
+ ax.set_xticks(xticks)
481
+ # Format tick labels: show integers without .0
482
+ tick_labels = [f'{int(x)}' if x == int(x) else f'{x}' for x in xticks]
483
+ ax.set_xticklabels(tick_labels, fontweight="bold")
484
+ ax.set_yticks([])
485
+
486
+ # --- Smart legend visibility ---
487
+ # Only show legend if there are items to display
488
+ show_legend = len(lines) > 0
489
+
490
+ if show_legend:
491
+ # Check for overlap to decide legend position
492
+ # Simplified overlap check for now
493
+ loc, ncol = "upper right", 1
494
+
495
+ # If spin polarized, upper right might cover spin up data
496
+ # But usually it's fine.
497
+
498
+ legend = ax.legend(
499
+ lines, labels,
500
+ frameon=False,
501
+ fontsize=13,
502
+ loc=loc,
503
+ ncol=ncol,
504
+ handlelength=1.5,
505
+ columnspacing=0.8,
506
+ handletextpad=0.6,
507
+ )
508
+ for text in legend.get_texts():
509
+ text.set_fontweight("bold")
510
+
511
+ ax.xaxis.set_minor_locator(AutoMinorLocator(2))
512
+ plt.tight_layout(pad=0.4)
513
+ plt.savefig(out, dpi=dpi)
514
+ plt.close(fig)
valyte/kpoints.py ADDED
@@ -0,0 +1,96 @@
1
+ """
2
+ Interactive K-Point Generation Module
3
+ =====================================
4
+
5
+ Handles interactive generation of KPOINTS files based on user input and structure.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import numpy as np
11
+ from pymatgen.core import Structure
12
+ from pymatgen.io.vasp.inputs import Kpoints
13
+
14
+ def generate_kpoints_interactive():
15
+ """
16
+ Interactively generates a KPOINTS file based on user input and POSCAR.
17
+ """
18
+ print("\n🔮 Valyte K-Point Generator")
19
+
20
+ # Check for POSCAR
21
+ poscar_path = "POSCAR"
22
+ if not os.path.exists(poscar_path):
23
+ # Try finding any POSCAR* file
24
+ files = [f for f in os.listdir('.') if f.startswith('POSCAR')]
25
+ if files:
26
+ poscar_path = files[0]
27
+ print(f" Found structure: {poscar_path}")
28
+ else:
29
+ print("❌ POSCAR file not found in current directory.")
30
+ return
31
+
32
+ try:
33
+ structure = Structure.from_file(poscar_path)
34
+ except Exception as e:
35
+ print(f"❌ Error reading structure: {e}")
36
+ return
37
+
38
+ # Scheme Selection
39
+ print("\nSelect K-Mesh Scheme:")
40
+ print(" 1. Monkhorst-Pack")
41
+ print(" 2. Gamma (Default)")
42
+
43
+ choice = input(" > ").strip()
44
+
45
+ if choice == '1':
46
+ scheme = 'MP'
47
+ else:
48
+ scheme = 'Gamma'
49
+
50
+ # K-Spacing Input
51
+ print("\nEnter K-Spacing (units of 2π/Å):")
52
+ print(" (Typical values: 0.03 - 0.04)")
53
+
54
+ try:
55
+ kspacing_str = input(" > ").strip()
56
+ if not kspacing_str:
57
+ kspacing = 0.04 # Default
58
+ else:
59
+ kspacing = float(kspacing_str)
60
+ except ValueError:
61
+ print("❌ Invalid number. Exiting.")
62
+ return
63
+
64
+ if kspacing <= 0:
65
+ print("ℹ️ Using Gamma-Only (1 1 1)")
66
+ kpts = Kpoints.gamma_automatic((1, 1, 1))
67
+ grid = (1, 1, 1)
68
+ else:
69
+ # Calculate grid based on spacing
70
+ # Formula: N = |b| / (spacing * 2*pi)
71
+ # pymatgen reciprocal lattice lengths include the 2*pi factor.
72
+
73
+ recip_lattice = structure.lattice.reciprocal_lattice
74
+ b_lengths = recip_lattice.abc
75
+
76
+ # We multiply spacing by 2*pi because the input is a coefficient of 2*pi/A?
77
+ # Or rather, standard convention (like VASP KSPACING) often implies 2*pi is involved in the density.
78
+ # Empirically: N = |b| / (input * 2*pi) matches expected results.
79
+ grid = [max(1, int(l / (kspacing * 2 * np.pi) + 0.5)) for l in b_lengths]
80
+
81
+ # Create Kpoints object
82
+ if scheme == 'MP':
83
+ kpts = Kpoints.monkhorst_automatic(grid)
84
+ else:
85
+ kpts = Kpoints.gamma_automatic(grid)
86
+
87
+ # Print Summary
88
+ print("\n📊 Summary")
89
+ print(f" Structure: {structure.formula}")
90
+ print(f" Lattice: a={structure.lattice.a:.2f}, b={structure.lattice.b:.2f}, c={structure.lattice.c:.2f} Å")
91
+ print(f" K-Mesh: {grid[0]} x {grid[1]} x {grid[2]} ({scheme})")
92
+
93
+ # Write KPOINTS
94
+ output_file = "KPOINTS"
95
+ kpts.write_file(output_file)
96
+ print(f"\n✅ Generated {output_file}!")
valyte/supercell.py ADDED
@@ -0,0 +1,35 @@
1
+ """
2
+ Supercell generation module for Valyte.
3
+ """
4
+
5
+ import os
6
+ from pymatgen.core import Structure
7
+
8
+
9
+ def create_supercell(poscar_path="POSCAR", nx=1, ny=1, nz=1, output="POSCAR_supercell"):
10
+ """
11
+ Creates a supercell from a POSCAR file.
12
+
13
+ Args:
14
+ poscar_path (str): Path to input POSCAR file.
15
+ nx (int): Supercell size in x direction.
16
+ ny (int): Supercell size in y direction.
17
+ nz (int): Supercell size in z direction.
18
+ output (str): Output filename for the supercell POSCAR.
19
+ """
20
+
21
+ if not os.path.exists(poscar_path):
22
+ raise FileNotFoundError(f"{poscar_path} not found")
23
+
24
+ # Read structure
25
+ structure = Structure.from_file(poscar_path)
26
+
27
+ # Create supercell
28
+ supercell = structure.copy()
29
+ supercell.make_supercell([nx, ny, nz])
30
+
31
+ # Write output
32
+ supercell.to(filename=output, fmt="poscar")
33
+
34
+ supercell_atoms = len(supercell)
35
+ print(f"✅ Supercell created: {output} ({supercell_atoms} atoms)")
valyte/valyte_band.png ADDED
Binary file
valyte/valyte_dos.png ADDED
Binary file
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.2
2
+ Name: valyte
3
+ Version: 0.1.0
4
+ Summary: A CLI tool for VASP post-processing (DOS plotting)
5
+ Home-page: https://github.com/nikyadav002/Valyte-Project
6
+ Author: Nikhil
7
+ Author-email: nikhil@example.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.6
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: numpy
14
+ Requires-Dist: matplotlib
15
+ Requires-Dist: pymatgen
16
+ Dynamic: author
17
+ Dynamic: author-email
18
+ Dynamic: classifier
19
+ Dynamic: description
20
+ Dynamic: description-content-type
21
+ Dynamic: home-page
22
+ Dynamic: requires-dist
23
+ Dynamic: requires-python
24
+ Dynamic: summary
25
+
26
+ <p align="center">
27
+ <img src="valyte/Logo.png" alt="Valyte Logo" width="100%"/>
28
+ </p>
29
+
30
+ # Valyte
31
+
32
+ **Valyte** is a comprehensive CLI tool for VASP workflows, providing both pre-processing and post-processing capabilities with a focus on clean, publication-quality outputs and modern aesthetics.
33
+
34
+ ## Features
35
+
36
+ ### Pre-processing
37
+ - **Supercell Creation**: Generate supercells from POSCAR files.
38
+ - **Interactive K-Point Generation**: Create KPOINTS files with automatic grid calculation based on K-spacing.
39
+ - **Band KPOINTS Generation**: Automatic high-symmetry path detection for band structure calculations.
40
+
41
+ ### Post-processing
42
+ - **DOS Plotting**:
43
+ - Smart Plotting: Automatically handles total DOS and Projected DOS (PDOS).
44
+ - Orbital-Resolved: Plots individual orbitals (s, p, d, f) by default.
45
+ - Adaptive Legend: Intelligently hides the legend if PDOS contributions are low.
46
+ - Gradient Fill: Aesthetically pleasing gradient fills for DOS peaks.
47
+ - **Band Structure Plotting**:
48
+ - VBM alignment to 0 eV.
49
+ - Color-coded bands (Purple for VB, Teal for CB).
50
+ - High-symmetry path labels from KPOINTS.
51
+ - **Publication Quality**: Clean aesthetics, custom fonts (Arial, Helvetica, Times New Roman), high DPI output.
52
+
53
+ ## Installation
54
+
55
+ Clone the repository and install in editable mode:
56
+
57
+ ```bash
58
+ git clone https://github.com/nikyadav002/Valyte-Project
59
+ cd Valyte-Project
60
+ pip install -e .
61
+ ```
62
+
63
+ ## Examples
64
+
65
+ <p align="center">
66
+ <img src="valyte/valyte_dos.png" alt="DOS Plot Example" width="47%"/>
67
+ <img src="valyte/valyte_band.png" alt="Band Structure Example" width="38%"/>
68
+ </p>
69
+
70
+ ## Updating Valyte
71
+
72
+ To update to the latest version:
73
+
74
+ ```bash
75
+ cd Valyte-Project
76
+ git pull
77
+ pip install -e .
78
+ ```
79
+
80
+ ## Usage
81
+
82
+ The main command is `valyte`.
83
+
84
+ <details>
85
+ <summary><strong>Click to view detailed usage instructions</strong></summary>
86
+
87
+ <br>
88
+
89
+ ### 🧊 Create Supercell
90
+
91
+ Generate a supercell from a POSCAR file:
92
+
93
+ ```bash
94
+ valyte supercell nx ny nz [options]
95
+ ```
96
+
97
+ **Example:**
98
+ ```bash
99
+ # Create a 2×2×2 supercell
100
+ valyte supercell 2 2 2
101
+
102
+ # Specify input and output files
103
+ valyte supercell 3 3 1 -i POSCAR_primitive -o POSCAR_3x3x1
104
+ ```
105
+
106
+ **Options:**
107
+ - `-i`, `--input`: Input POSCAR file (default: `POSCAR`).
108
+ - `-o`, `--output`: Output filename (default: `POSCAR_supercell`).
109
+
110
+ ---
111
+
112
+ ### 📉 Band Structure
113
+
114
+ #### 1. Generate KPOINTS
115
+
116
+ Automatically generate a KPOINTS file with high-symmetry paths for band structure calculations.
117
+
118
+ ```bash
119
+ valyte band kpt-gen [options]
120
+ ```
121
+
122
+ **Options:**
123
+ - `-i`, `--input`: Input POSCAR file (default: `POSCAR`).
124
+ - `-n`, `--npoints`: Points per segment (default: `40`).
125
+ - `-o`, `--output`: Output filename (default: `KPOINTS`).
126
+
127
+ **Example:**
128
+ ```bash
129
+ valyte band kpt-gen -n 60 -i POSCAR_relaxed -o KPOINTS_band
130
+ ```
131
+
132
+ ### 🕸️ Generate K-Points (Interactive)
133
+
134
+ Generate a `KPOINTS` file for SCF calculations interactively.
135
+
136
+ ```bash
137
+ valyte kpt
138
+ ```
139
+
140
+ This command will prompt you for:
141
+ 1. **K-Mesh Scheme**: Monkhorst-Pack or Gamma.
142
+ 2. **K-Spacing**: Value in $2\pi/\AA$ (e.g., 0.04).
143
+
144
+ It automatically calculates the optimal grid based on your `POSCAR` structure.
145
+
146
+ #### 2. Plot Band Structure
147
+
148
+ Plot the electronic band structure from `vasprun.xml`.
149
+
150
+ ```bash
151
+ valyte band [options]
152
+ ```
153
+
154
+ **Options:**
155
+ - `--vasprun`: Path to `vasprun.xml` (default: current directory).
156
+ - `--kpoints`: Path to `KPOINTS` file for path labels (default: looks for `KPOINTS` in same dir).
157
+ - `--ylim`: Energy range, e.g., `--ylim -4 4`.
158
+ - `-o, --output`: Output filename (default: `valyte_band.png`).
159
+
160
+ **Example:**
161
+ ```bash
162
+ valyte band --ylim -3 3 -o my_bands.png
163
+ ```
164
+
165
+ ---
166
+
167
+ ### 📊 Plot DOS
168
+
169
+ ```bash
170
+ valyte dos [path/to/vasprun.xml] [options]
171
+ ```
172
+
173
+ You can provide the path as a positional argument, use the `--vasprun` flag, or omit it to use the current directory.
174
+
175
+ **Examples:**
176
+ ```bash
177
+ # Plot all orbitals for all elements (Default)
178
+ valyte dos
179
+
180
+ # Plot specific elements (Total PDOS)
181
+ valyte dos -e Fe O
182
+
183
+ # Plot specific orbitals
184
+ valyte dos -e "Fe(d)" "O(p)"
185
+
186
+ # Plot mixed (Fe Total and Fe d-orbital)
187
+ valyte dos -e Fe "Fe(d)"
188
+ ```
189
+
190
+ **Options:**
191
+ - `-e`, `--elements`: Specific elements or orbitals to plot.
192
+ - Example: `-e Fe O` (Plots Total PDOS for Fe and O).
193
+ - Example: `-e Fe(d) O(p)` (Plots Fe d-orbital and O p-orbital).
194
+ - Example: `-e Fe Fe(d)` (Plots Fe Total and Fe d-orbital).
195
+ - `--xlim`: Energy range (default: `-6 6`).
196
+ - `--ylim`: DOS range (e.g., `--ylim 0 10`).
197
+ - `--scale`: Scaling factor for Y-axis (e.g., `--scale 3` divides DOS by 3).
198
+ - `--fermi`: Draw a dashed line at the Fermi level (E=0). Default is OFF.
199
+ - `--pdos`: Plot only Projected DOS (hide Total DOS).
200
+ - `--legend-cutoff`: Threshold for legend visibility (default: `0.10` = 10%).
201
+ - `-o`, `--output`: Output filename (default: `valyte_dos.png`).
202
+ - `--font`: Font family (default: `Arial`).
203
+
204
+ **Example:**
205
+
206
+ ```bash
207
+ valyte dos ./vasp_data --xlim -5 5 -o my_dos.png
208
+ ```
209
+
210
+ </details>
@@ -0,0 +1,15 @@
1
+ valyte/Logo.png,sha256=HSZQsjsCj4y_8zeXE1kR1W7deb-6gXheEnmcLcSKUxw,4327936
2
+ valyte/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ valyte/band.py,sha256=qipx3YIlcl2yV-g6nn_YPRJCidvlrxZKEQzSRyBkwac,1917
4
+ valyte/band_plot.py,sha256=2jP6fEh8qDYHXxDAs4S69xDcxrzWbYcjOAWiGHwjyF4,4766
5
+ valyte/cli.py,sha256=c5At8G4t6IoZEQKEJdBP72TzsKweUX5DIiaaUd9aQXg,8847
6
+ valyte/dos_plot.py,sha256=ddEjLFlBs6acHZygaLPBEGVti6qTzwM4pO1zF-pGFUU,17687
7
+ valyte/kpoints.py,sha256=_LISADqe11NBlv8LMjMkF5rWrREHB3aU5-nHvqxj3jk,3055
8
+ valyte/supercell.py,sha256=w6Ik_krXoshgliJDiyjoIZXuifzN0ydi6VSmpzutm9Y,996
9
+ valyte/valyte_band.png,sha256=1Bh-x7qvl1j4D9HGGbQK8OlMUrTU1mhU_kMILUsNiD8,246677
10
+ valyte/valyte_dos.png,sha256=ViE4CycCSqFi_ZtUhA7oGI1nTyt0mHoYI6yg5-Et35k,182523
11
+ valyte-0.1.0.dist-info/METADATA,sha256=0wSkS7JBoPQd2Rcm0SfC_I45pfxuV_TDFyixkfrc4JQ,5539
12
+ valyte-0.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
13
+ valyte-0.1.0.dist-info/entry_points.txt,sha256=Ny3Z5rh3Ia7lEKoMDDZOm4_jS-Zde3qFHv8f1GLUdxk,43
14
+ valyte-0.1.0.dist-info/top_level.txt,sha256=72-UqyU15JSWDjtBQf6cY0_UBqz0EU2FoVeXjd1JZ5M,7
15
+ valyte-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.8.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ valyte = valyte.cli:main
@@ -0,0 +1 @@
1
+ valyte