valyte 0.1.9__tar.gz → 0.1.11__tar.gz

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 (32) hide show
  1. {valyte-0.1.9/valyte.egg-info → valyte-0.1.11}/PKG-INFO +60 -1
  2. {valyte-0.1.9 → valyte-0.1.11}/README.md +59 -0
  3. valyte-0.1.11/pyproject.toml +3 -0
  4. {valyte-0.1.9 → valyte-0.1.11}/setup.py +1 -1
  5. valyte-0.1.11/valyte/band.py +247 -0
  6. valyte-0.1.11/valyte/band_plot.py +86 -0
  7. {valyte-0.1.9 → valyte-0.1.11}/valyte/cli.py +66 -81
  8. valyte-0.1.11/valyte/dos_plot.py +380 -0
  9. valyte-0.1.11/valyte/ipr.py +186 -0
  10. valyte-0.1.11/valyte/kpoints.py +80 -0
  11. valyte-0.1.11/valyte/potcar.py +36 -0
  12. valyte-0.1.11/valyte/supercell.py +18 -0
  13. {valyte-0.1.9 → valyte-0.1.11/valyte.egg-info}/PKG-INFO +60 -1
  14. {valyte-0.1.9 → valyte-0.1.11}/valyte.egg-info/SOURCES.txt +2 -0
  15. valyte-0.1.9/valyte/band.py +0 -289
  16. valyte-0.1.9/valyte/band_plot.py +0 -127
  17. valyte-0.1.9/valyte/dos_plot.py +0 -549
  18. valyte-0.1.9/valyte/kpoints.py +0 -108
  19. valyte-0.1.9/valyte/potcar.py +0 -61
  20. valyte-0.1.9/valyte/supercell.py +0 -35
  21. {valyte-0.1.9 → valyte-0.1.11}/MANIFEST.in +0 -0
  22. {valyte-0.1.9 → valyte-0.1.11}/setup.cfg +0 -0
  23. {valyte-0.1.9 → valyte-0.1.11}/valyte/Logo.png +0 -0
  24. {valyte-0.1.9 → valyte-0.1.11}/valyte/__init__.py +0 -0
  25. {valyte-0.1.9 → valyte-0.1.11}/valyte/data/__init__.py +0 -0
  26. {valyte-0.1.9 → valyte-0.1.11}/valyte/data/bradcrack.json +0 -0
  27. {valyte-0.1.9 → valyte-0.1.11}/valyte/valyte_band.png +0 -0
  28. {valyte-0.1.9 → valyte-0.1.11}/valyte/valyte_dos.png +0 -0
  29. {valyte-0.1.9 → valyte-0.1.11}/valyte.egg-info/dependency_links.txt +0 -0
  30. {valyte-0.1.9 → valyte-0.1.11}/valyte.egg-info/entry_points.txt +0 -0
  31. {valyte-0.1.9 → valyte-0.1.11}/valyte.egg-info/requires.txt +0 -0
  32. {valyte-0.1.9 → valyte-0.1.11}/valyte.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: valyte
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: A comprehensive CLI tool for VASP pre-processing (Supercells, K-points) and post-processing (DOS, Band Structure plotting)
5
5
  Home-page: https://github.com/nikyadav002/Valyte-Project
6
6
  Author: Nikhil
@@ -42,6 +42,7 @@ Requires-Dist: seekpath
42
42
  - VBM alignment to 0 eV.
43
43
  - Color-coded bands (Purple for VB, Teal for CB).
44
44
  - High-symmetry path labels from KPOINTS.
45
+ - **IPR Analysis**: Compute Inverse Participation Ratio from PROCAR to analyze wavefunction localization.
45
46
  - **Publication Quality**: Clean aesthetics, custom fonts (Arial, Helvetica, Times New Roman), high DPI output.
46
47
 
47
48
  ## Installation
@@ -152,6 +153,38 @@ This command will prompt you for:
152
153
 
153
154
  It automatically calculates the optimal grid based on your `POSCAR` structure.
154
155
 
156
+ ---
157
+
158
+ ### 🧪 Generate POTCAR
159
+
160
+ Generate a `POTCAR` file based on the species in your `POSCAR`.
161
+
162
+ ```bash
163
+ valyte potcar [options]
164
+ ```
165
+
166
+ **Options:**
167
+ - `-i`, `--input`: Input POSCAR file (default: `POSCAR`).
168
+ - `-o`, `--output`: Output filename (default: `POTCAR`).
169
+ - `--functional`: Functional to use (default: `PBE`). Options include `PBE`, `PBE_52`, `PBE_54`, `LDA`, etc.
170
+
171
+ **Example:**
172
+ ```bash
173
+ # Generate POTCAR using default PBE functional
174
+ valyte potcar
175
+
176
+ # Use a specific functional
177
+ valyte potcar --functional PBE_54
178
+
179
+ # Specify input and output files
180
+ valyte potcar -i POSCAR_relaxed -o POTCAR_new
181
+ ```
182
+
183
+ > [!IMPORTANT]
184
+ > **Pymatgen Configuration Required**: This command requires Pymatgen to be configured with your VASP pseudopotential directory. Set `PMG_VASP_PSP_DIR` in your `~/.pmgrc.yaml` file. See the [Pymatgen documentation](https://pymatgen.org/installation.html#potcar-setup) for setup instructions.
185
+
186
+ ---
187
+
155
188
  #### 2. Plot Band Structure
156
189
 
157
190
  Plot the electronic band structure from `vasprun.xml`.
@@ -216,4 +249,30 @@ valyte dos -e Fe "Fe(d)"
216
249
  valyte dos ./vasp_data --xlim -5 5 -o my_dos.png
217
250
  ```
218
251
 
252
+ ---
253
+
254
+ ### 📐 Compute IPR (Inverse Participation Ratio)
255
+
256
+ Compute the Inverse Participation Ratio (IPR) from VASP `PROCAR` to analyze wavefunction localization.
257
+
258
+ ```bash
259
+ valyte ipr
260
+ ```
261
+
262
+ This interactive command will:
263
+ 1. Read the `PROCAR` file from the current directory.
264
+ 2. Display the number of k-points, bands, and atoms.
265
+ 3. Prompt you for **band indices** to analyze (e.g., `5 6 7` or `5-7`).
266
+ 4. Optionally show per-k-point IPR values.
267
+ 5. Save results to `ipr_procar.dat`.
268
+
269
+ **Output Columns:**
270
+ - **Band**: Band index.
271
+ - **Energy**: Average energy (eV) across k-points.
272
+ - **IPR**: Inverse Participation Ratio (higher = more localized).
273
+ - **N_eff**: Effective number of atoms (1/IPR).
274
+
275
+ > [!TIP]
276
+ > Use IPR to identify localized defect states. A state localized on a single atom has IPR ≈ 1.0 and N_eff ≈ 1. Delocalized band states have small IPR and large N_eff.
277
+
219
278
  </details>
@@ -23,6 +23,7 @@
23
23
  - VBM alignment to 0 eV.
24
24
  - Color-coded bands (Purple for VB, Teal for CB).
25
25
  - High-symmetry path labels from KPOINTS.
26
+ - **IPR Analysis**: Compute Inverse Participation Ratio from PROCAR to analyze wavefunction localization.
26
27
  - **Publication Quality**: Clean aesthetics, custom fonts (Arial, Helvetica, Times New Roman), high DPI output.
27
28
 
28
29
  ## Installation
@@ -133,6 +134,38 @@ This command will prompt you for:
133
134
 
134
135
  It automatically calculates the optimal grid based on your `POSCAR` structure.
135
136
 
137
+ ---
138
+
139
+ ### 🧪 Generate POTCAR
140
+
141
+ Generate a `POTCAR` file based on the species in your `POSCAR`.
142
+
143
+ ```bash
144
+ valyte potcar [options]
145
+ ```
146
+
147
+ **Options:**
148
+ - `-i`, `--input`: Input POSCAR file (default: `POSCAR`).
149
+ - `-o`, `--output`: Output filename (default: `POTCAR`).
150
+ - `--functional`: Functional to use (default: `PBE`). Options include `PBE`, `PBE_52`, `PBE_54`, `LDA`, etc.
151
+
152
+ **Example:**
153
+ ```bash
154
+ # Generate POTCAR using default PBE functional
155
+ valyte potcar
156
+
157
+ # Use a specific functional
158
+ valyte potcar --functional PBE_54
159
+
160
+ # Specify input and output files
161
+ valyte potcar -i POSCAR_relaxed -o POTCAR_new
162
+ ```
163
+
164
+ > [!IMPORTANT]
165
+ > **Pymatgen Configuration Required**: This command requires Pymatgen to be configured with your VASP pseudopotential directory. Set `PMG_VASP_PSP_DIR` in your `~/.pmgrc.yaml` file. See the [Pymatgen documentation](https://pymatgen.org/installation.html#potcar-setup) for setup instructions.
166
+
167
+ ---
168
+
136
169
  #### 2. Plot Band Structure
137
170
 
138
171
  Plot the electronic band structure from `vasprun.xml`.
@@ -197,4 +230,30 @@ valyte dos -e Fe "Fe(d)"
197
230
  valyte dos ./vasp_data --xlim -5 5 -o my_dos.png
198
231
  ```
199
232
 
233
+ ---
234
+
235
+ ### 📐 Compute IPR (Inverse Participation Ratio)
236
+
237
+ Compute the Inverse Participation Ratio (IPR) from VASP `PROCAR` to analyze wavefunction localization.
238
+
239
+ ```bash
240
+ valyte ipr
241
+ ```
242
+
243
+ This interactive command will:
244
+ 1. Read the `PROCAR` file from the current directory.
245
+ 2. Display the number of k-points, bands, and atoms.
246
+ 3. Prompt you for **band indices** to analyze (e.g., `5 6 7` or `5-7`).
247
+ 4. Optionally show per-k-point IPR values.
248
+ 5. Save results to `ipr_procar.dat`.
249
+
250
+ **Output Columns:**
251
+ - **Band**: Band index.
252
+ - **Energy**: Average energy (eV) across k-points.
253
+ - **IPR**: Inverse Participation Ratio (higher = more localized).
254
+ - **N_eff**: Effective number of atoms (1/IPR).
255
+
256
+ > [!TIP]
257
+ > Use IPR to identify localized defect states. A state localized on a single atom has IPR ≈ 1.0 and N_eff ≈ 1. Delocalized band states have small IPR and large N_eff.
258
+
200
259
  </details>
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="valyte",
5
- version="0.1.9",
5
+ version="0.1.11",
6
6
  packages=find_packages(),
7
7
  install_requires=[
8
8
  "numpy",
@@ -0,0 +1,247 @@
1
+ """Band structure KPOINTS generation."""
2
+
3
+ import os
4
+ import json
5
+ import numpy as np
6
+ import seekpath
7
+ from pymatgen.core import Structure
8
+ from pymatgen.symmetry.bandstructure import HighSymmKpath
9
+ from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
10
+ try:
11
+ from importlib.resources import files as ilr_files
12
+ except ImportError:
13
+ from importlib_resources import files as ilr_files
14
+
15
+ from valyte.potcar import generate_potcar
16
+
17
+
18
+ def generate_band_kpoints(poscar_path="POSCAR", npoints=40, output="KPOINTS", symprec=0.01, mode="bradcrack"):
19
+ """Generate a line-mode KPOINTS file for band structure calculations."""
20
+
21
+ if not os.path.exists(poscar_path):
22
+ raise FileNotFoundError(f"{poscar_path} not found")
23
+
24
+ mode = (mode or "bradcrack").lower()
25
+
26
+ structure = Structure.from_file(poscar_path)
27
+
28
+ if mode == "bradcrack":
29
+ try:
30
+ kpath = BradCrackKpath(structure, symprec=symprec)
31
+ prim_std = kpath.prim
32
+ path = kpath.path
33
+ kpoints = kpath.kpoints
34
+
35
+ standard_filename = "POSCAR_standard"
36
+ prim_std.to(filename=standard_filename)
37
+ except Exception as e:
38
+ raise RuntimeError(f"Error generating Bradley-Cracknell path: {e}")
39
+ else:
40
+ try:
41
+ if mode == "seekpath":
42
+ mode = "hinuma"
43
+
44
+ sga = SpacegroupAnalyzer(structure, symprec=symprec)
45
+ prim_std = sga.get_primitive_standard_structure()
46
+ except Exception as e:
47
+ raise RuntimeError(f"Error during standardization: {e}")
48
+
49
+ try:
50
+ kpath = HighSymmKpath(prim_std, path_type=mode, symprec=symprec)
51
+
52
+ standard_filename = "POSCAR_standard"
53
+ prim_std.to(filename=standard_filename)
54
+
55
+ path = kpath.kpath["path"]
56
+ kpoints = kpath.kpath["kpoints"]
57
+ except Exception as e:
58
+ raise RuntimeError(f"Error generating K-path: {e}")
59
+
60
+ try:
61
+ with open(output, "w") as f:
62
+ f.write("KPOINTS for Band Structure\n")
63
+ f.write(f"{npoints}\n")
64
+ f.write("Line-mode\n")
65
+ f.write("Reciprocal\n")
66
+
67
+ for subpath in path:
68
+ for i in range(len(subpath) - 1):
69
+ start_label = subpath[i]
70
+ end_label = subpath[i + 1]
71
+
72
+ start_coords = kpoints[start_label]
73
+ end_coords = kpoints[end_label]
74
+
75
+ f.write(
76
+ f"{start_coords[0]:10.6f} {start_coords[1]:10.6f} {start_coords[2]:10.6f} ! {start_label}\n"
77
+ )
78
+ f.write(
79
+ f"{end_coords[0]:10.6f} {end_coords[1]:10.6f} {end_coords[2]:10.6f} ! {end_label}\n"
80
+ )
81
+ f.write("\n")
82
+
83
+ print(f"Generated {output} ({' - '.join([' - '.join(seg) for seg in path])})")
84
+ print(f"Generated {standard_filename} (Standardized Primitive Cell)")
85
+ print("IMPORTANT: Use POSCAR_standard for the band calculation.")
86
+ except Exception as e:
87
+ raise RuntimeError(f"Error writing KPOINTS file: {e}")
88
+
89
+ try:
90
+ print("Generating default POTCAR (PBE)...")
91
+ generate_potcar(poscar_path=poscar_path, functional="PBE", output="POTCAR")
92
+ except Exception as e:
93
+ print(f"Warning: could not generate POTCAR: {e}")
94
+ print("Proceeding without POTCAR generation.")
95
+
96
+
97
+ class BradCrackKpath:
98
+ """Bradley-Cracknell K-path generation using SeeK-path output."""
99
+
100
+ def __init__(self, structure, symprec=0.01):
101
+ self.structure = structure
102
+ self.symprec = symprec
103
+
104
+ sga = SpacegroupAnalyzer(structure, symprec=symprec)
105
+ self._spg_data = sga.get_symmetry_dataset()
106
+
107
+ cell = (
108
+ structure.lattice.matrix,
109
+ structure.frac_coords,
110
+ [s.specie.number for s in structure],
111
+ )
112
+
113
+ self._seek_data = seekpath.get_path(cell, symprec=symprec)
114
+
115
+ prim_lattice = self._seek_data["primitive_lattice"]
116
+ prim_pos = self._seek_data["primitive_positions"]
117
+ prim_types = self._seek_data["primitive_types"]
118
+
119
+ z_to_specie = {s.specie.number: s.specie for s in structure}
120
+ prim_species = [z_to_specie[z] for z in prim_types]
121
+
122
+ self.prim = Structure(prim_lattice, prim_species, prim_pos)
123
+
124
+ conv_lattice = self._seek_data["conv_lattice"]
125
+ conv_pos = self._seek_data["conv_positions"]
126
+ conv_types = self._seek_data["conv_types"]
127
+ conv_species = [z_to_specie[z] for z in conv_types]
128
+ self.conv = Structure(conv_lattice, conv_species, conv_pos)
129
+
130
+ self._get_bradcrack_path()
131
+
132
+ def _get_bradcrack_path(self):
133
+ a, b, c = self.conv.lattice.abc
134
+ angles = self.conv.lattice.angles
135
+
136
+ angles_r = [round(x, 3) for x in angles]
137
+ unique_val = min(angles_r, key=angles_r.count)
138
+ unique = angles_r.index(unique_val)
139
+
140
+ spg_symbol = self._spg_data["international"]
141
+ spg_number = self._spg_data["number"]
142
+
143
+ lattice_type = self.get_lattice_type(spg_number)
144
+ bravais = self._get_bravais_lattice(spg_symbol, lattice_type, a, b, c, unique)
145
+
146
+ json_file = ilr_files("valyte.data").joinpath("bradcrack.json")
147
+ with json_file.open("r") as f:
148
+ data = json.load(f)
149
+
150
+ if bravais not in data:
151
+ raise ValueError(f"Bravais lattice code '{bravais}' not found in BradCrack data.")
152
+
153
+ self.bradcrack_data = data[bravais]
154
+ self.kpoints = self.bradcrack_data["kpoints"]
155
+ self.path = self.bradcrack_data["path"]
156
+
157
+ def get_lattice_type(self, number):
158
+ if 1 <= number <= 2:
159
+ return "triclinic"
160
+ if 3 <= number <= 15:
161
+ return "monoclinic"
162
+ if 16 <= number <= 74:
163
+ return "orthorhombic"
164
+ if 75 <= number <= 142:
165
+ return "tetragonal"
166
+ if 143 <= number <= 167:
167
+ if number in [146, 148, 155, 160, 161, 166, 167]:
168
+ return "rhombohedral"
169
+ return "trigonal"
170
+ if 168 <= number <= 194:
171
+ return "hexagonal"
172
+ if 195 <= number <= 230:
173
+ return "cubic"
174
+ return "unknown"
175
+
176
+ def _get_bravais_lattice(self, spg_symbol, lattice_type, a, b, c, unique):
177
+ if lattice_type == "triclinic":
178
+ return "triclinic"
179
+
180
+ if lattice_type == "monoclinic":
181
+ if "P" in spg_symbol:
182
+ if unique == 0:
183
+ return "mon_p_a"
184
+ if unique == 1:
185
+ return "mon_p_b"
186
+ if unique == 2:
187
+ return "mon_p_c"
188
+ if "C" in spg_symbol:
189
+ if unique == 0:
190
+ return "mon_c_a"
191
+ if unique == 1:
192
+ return "mon_c_b"
193
+ if unique == 2:
194
+ return "mon_c_c"
195
+
196
+ if lattice_type == "orthorhombic":
197
+ if "P" in spg_symbol:
198
+ return "orth_p"
199
+ if "A" in spg_symbol or "C" in spg_symbol:
200
+ if a > b:
201
+ return "orth_c_a"
202
+ if b > a:
203
+ return "orth_c_b"
204
+ if "F" in spg_symbol:
205
+ inv_a2 = 1 / a**2
206
+ inv_b2 = 1 / b**2
207
+ inv_c2 = 1 / c**2
208
+ if (inv_a2 < inv_b2 + inv_c2) and (inv_b2 < inv_c2 + inv_a2) and (inv_c2 < inv_a2 + inv_b2):
209
+ return "orth_f_1"
210
+ if inv_c2 > inv_a2 + inv_b2:
211
+ return "orth_f_2"
212
+ if inv_b2 > inv_a2 + inv_c2:
213
+ return "orth_f_3"
214
+ if inv_a2 > inv_c2 + inv_b2:
215
+ return "orth_f_4"
216
+ if "I" in spg_symbol:
217
+ if a > b and a > c:
218
+ return "orth_i_a"
219
+ if b > a and b > c:
220
+ return "orth_i_b"
221
+ if c > a and c > b:
222
+ return "orth_i_c"
223
+
224
+ if lattice_type == "tetragonal":
225
+ if "P" in spg_symbol:
226
+ return "tet_p"
227
+ if "I" in spg_symbol:
228
+ return "tet_i_a" if a > c else "tet_i_c"
229
+
230
+ if lattice_type in ["trigonal", "hexagonal", "rhombohedral"]:
231
+ if "R" in spg_symbol:
232
+ return "trig_r_a" if a > np.sqrt(2) * c else "trig_r_c"
233
+ if "P" in spg_symbol:
234
+ if unique == 0:
235
+ return "trig_p_a"
236
+ if unique == 2:
237
+ return "trig_p_c"
238
+
239
+ if lattice_type == "cubic":
240
+ if "P" in spg_symbol:
241
+ return "cubic_p"
242
+ if "I" in spg_symbol:
243
+ return "cubic_i"
244
+ if "F" in spg_symbol:
245
+ return "cubic_f"
246
+
247
+ return "unknown"
@@ -0,0 +1,86 @@
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 BSVasprun
8
+ from pymatgen.electronic_structure.plotter import BSPlotter
9
+
10
+
11
+ def plot_band_structure(vasprun_path, kpoints_path=None, output="valyte_band.png",
12
+ ylim=None, figsize=(4, 4), dpi=400, font="Arial"):
13
+ """Plot the electronic band structure from a VASP vasprun.xml."""
14
+
15
+ if os.path.isdir(vasprun_path):
16
+ vasprun_path = os.path.join(vasprun_path, "vasprun.xml")
17
+
18
+ font_map = {
19
+ "arial": "Arial",
20
+ "helvetica": "Helvetica",
21
+ "times": "Times New Roman",
22
+ "times new roman": "Times New Roman",
23
+ }
24
+ font = font_map.get(font.lower(), "Arial")
25
+ mpl.rcParams["font.family"] = font
26
+ mpl.rcParams["axes.linewidth"] = 1.4
27
+ mpl.rcParams["font.weight"] = "bold"
28
+ mpl.rcParams["font.size"] = 14
29
+ mpl.rcParams["xtick.major.width"] = 1.2
30
+ mpl.rcParams["ytick.major.width"] = 1.2
31
+
32
+ try:
33
+ vr = BSVasprun(vasprun_path, parse_projected_eigen=False)
34
+ bs = vr.get_band_structure(kpoints_filename=kpoints_path, line_mode=True)
35
+ except Exception as e:
36
+ raise ValueError(f"Failed to load band structure: {e}")
37
+
38
+ bs_plotter = BSPlotter(bs)
39
+ data = bs_plotter.bs_plot_data(zero_to_efermi=True)
40
+
41
+ distances = data["distances"]
42
+ energies = data["energy"]
43
+ ticks = data["ticks"]
44
+
45
+ fig, ax = plt.subplots(figsize=figsize)
46
+
47
+ color_vb = "#8e44ad"
48
+ color_cb = "#2a9d8f"
49
+
50
+ for i in range(len(distances)):
51
+ d = distances[i]
52
+
53
+ if isinstance(energies, dict):
54
+ for spin in energies:
55
+ for band in energies[spin][i]:
56
+ c = color_vb if np.mean(band) <= 0 else color_cb
57
+ ax.plot(d, band, color=c, lw=1.5, alpha=1.0)
58
+ else:
59
+ for spin in energies[i]:
60
+ for band in energies[i][spin]:
61
+ c = color_vb if np.mean(band) <= 0 else color_cb
62
+ ax.plot(d, band, color=c, lw=1.5, alpha=1.0)
63
+
64
+ ax.set_xticks(ticks["distance"])
65
+ clean_labels = [(l or "").replace("$\\mid$", "|") for l in ticks["label"]]
66
+ ax.set_xticklabels(clean_labels, fontsize=14, fontweight="bold")
67
+
68
+ for d in ticks["distance"]:
69
+ ax.axvline(d, color="k", lw=0.8, ls="-", alpha=0.3)
70
+
71
+ ax.axhline(0, color="k", lw=0.8, ls="--", alpha=0.5)
72
+
73
+ ax.set_ylabel("Energy (eV)", fontsize=16, fontweight="bold", labelpad=8)
74
+ if ylim:
75
+ ax.set_ylim(ylim)
76
+ yticks = np.arange(np.ceil(ylim[0]), np.floor(ylim[1]) + 1, 1)
77
+ ax.set_yticks(yticks)
78
+ else:
79
+ ax.set_ylim(-4, 4)
80
+ ax.set_yticks(np.arange(-4, 5, 1))
81
+
82
+ ax.set_xlim(distances[0][0], distances[-1][-1])
83
+
84
+ plt.tight_layout()
85
+ plt.savefig(output, dpi=dpi)
86
+ plt.close(fig)