ipyvasp 0.9.83__tar.gz → 0.9.85__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 (30) hide show
  1. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/PKG-INFO +1 -1
  2. ipyvasp-0.9.85/ipyvasp/_version.py +1 -0
  3. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/core/parser.py +35 -28
  4. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/core/plot_toolkit.py +4 -1
  5. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/core/serializer.py +2 -2
  6. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/lattice.py +6 -5
  7. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/misc.py +4 -6
  8. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/utils.py +3 -3
  9. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/widgets.py +64 -24
  10. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp.egg-info/PKG-INFO +1 -1
  11. ipyvasp-0.9.83/ipyvasp/_version.py +0 -1
  12. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/LICENSE +0 -0
  13. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/README.md +0 -0
  14. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/__init__.py +0 -0
  15. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/__main__.py +0 -0
  16. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/_enplots.py +0 -0
  17. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/_lattice.py +0 -0
  18. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/bsdos.py +0 -0
  19. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/cli.py +0 -0
  20. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/core/__init__.py +0 -0
  21. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/core/spatial_toolkit.py +0 -0
  22. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/evals_dataframe.py +0 -0
  23. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp/potential.py +0 -0
  24. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp.egg-info/SOURCES.txt +0 -0
  25. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp.egg-info/dependency_links.txt +0 -0
  26. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp.egg-info/entry_points.txt +0 -0
  27. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp.egg-info/requires.txt +0 -0
  28. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/ipyvasp.egg-info/top_level.txt +0 -0
  29. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/setup.cfg +0 -0
  30. {ipyvasp-0.9.83 → ipyvasp-0.9.85}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipyvasp
3
- Version: 0.9.83
3
+ Version: 0.9.85
4
4
  Summary: A processing tool for VASP DFT input/output processing in Jupyter Notebook.
5
5
  Home-page: https://github.com/massgh/ipyvasp
6
6
  Author: Abdul Saboor
@@ -0,0 +1 @@
1
+ __version__ = "0.9.85"
@@ -1,4 +1,4 @@
1
- __all__ = ["Vasprun", "Vaspout", "minify_vasprun", "xml2dict"]
1
+ __all__ = ["Vasprun", "Vaspout", "minify_vasprun", "xml2dict","read"]
2
2
 
3
3
  import re
4
4
  from io import StringIO
@@ -114,6 +114,35 @@ class Vaspout(DataSource):
114
114
  def __init__(self, path):
115
115
  raise NotImplementedError("Vaspout is not implemented yet.")
116
116
 
117
+ def read(file, start_match, stop_match=r'\n', nth_match=1, skip_last=False,apply=None):
118
+ """Reads a part of the file between start_match and stop_match and returns a generator. It is lazy and fast.
119
+ `start_match` and `stop_match`(default is end of same line) are regular expressions. `nth_match` is the number of occurence of start_match to start reading.
120
+ `skip_last` is used to determine whether to keep or skip last line.
121
+ `apply` should be None or `func` to transform each captured line.
122
+ """
123
+ if "|" in start_match:
124
+ raise ValueError(
125
+ "start_match should be a single match, so '|' character is not allowed."
126
+ )
127
+ with Path(file).open("r") as f:
128
+ lines = islice(f, None) # this is fast
129
+ matched = False
130
+ n_start = 1
131
+ for line in lines:
132
+ if re.search(start_match, line, flags=re.DOTALL):
133
+ if nth_match != n_start:
134
+ n_start += 1
135
+ else:
136
+ matched = True
137
+ if matched and re.search(
138
+ stop_match, line, flags=re.DOTALL
139
+ ): # avoid stop before start
140
+ matched = False
141
+ if not skip_last:
142
+ yield apply(line) if callable(apply) else line
143
+ break # stop reading
144
+ if matched: # should be after break to handle last line above
145
+ yield apply(line) if callable(apply) else line
117
146
 
118
147
  class Vasprun(DataSource):
119
148
  "Reads vasprun.xml file lazily. It reads only the required data from the file when a plot or data access is requested."
@@ -129,36 +158,14 @@ class Vasprun(DataSource):
129
158
  skipk if isinstance(skipk, (int, np.integer)) else self.get_skipk()
130
159
  )
131
160
 
132
- def read(self, start_match, stop_match, nth_match=1, skip_last=False):
161
+ def read(self, start_match, stop_match=r'\n', nth_match=1, skip_last=False,apply=None):
133
162
  """Reads a part of the file between start_match and stop_match and returns a generator. It is lazy and fast.
134
- `start_match` and `stop_match` are regular expressions. `nth_match` is the number of occurence of start_match to start reading.
163
+ `start_match` and `stop_match`(default is end of same line) are regular expressions. `nth_match` is the number of occurence of start_match to start reading.
135
164
  `skip_last` is used to determine whether to keep or skip last line.
165
+ `apply` should be None or `func` to transform each captured line.
136
166
  """
137
- if "|" in start_match:
138
- raise ValueError(
139
- "start_match should be a single match, so '|' character is not allowed."
140
- )
141
-
142
- with self.path.open("r") as f:
143
- lines = islice(f, None) # this is fast
144
- matched = False
145
- n_start = 1
146
- for line in lines:
147
- if re.search(start_match, line, flags=re.DOTALL):
148
- if nth_match != n_start:
149
- n_start += 1
150
- else:
151
- matched = True
152
- if matched and re.search(
153
- stop_match, line, flags=re.DOTALL
154
- ): # avoid stop before start
155
- matched = False
156
- if not skip_last:
157
- yield line
158
-
159
- break # stop reading
160
- if matched: # should be after break to handle last line above
161
- yield line
167
+ kws = {k:v for k,v in locals().items() if k !='self'}
168
+ return read(self.path,**kws)
162
169
 
163
170
  def get_skipk(self):
164
171
  "Returns the number of k-points to skip in band structure plot in case of HSE calculation."
@@ -1009,7 +1009,10 @@ def iplot2html(fig, outfile=None, modebar=True):
1009
1009
 
1010
1010
 
1011
1011
  def iplot2widget(fig, fig_widget=None, template=None):
1012
- "Converts plotly's figure to FigureWidget by copying attributes and data. If fig_widget is provided, it will update it. Adds template if provided."
1012
+ "Converts plotly's figure to FigureWidget by copying attributes and data. If fig_widget is provided, it will update it. Adds template if provided. If fig is FigureWidget, it is just returned"
1013
+ if isinstance(fig, go.FigureWidget):
1014
+ return fig
1015
+
1013
1016
  if not isinstance(fig, go.Figure):
1014
1017
  raise ValueError("fig must be instance of plotly.graph_objects.Figure")
1015
1018
 
@@ -917,13 +917,13 @@ def dump(data, format: str = "pickle", outfile: str = None, indent: int = 1) ->
917
917
  if format == "pickle":
918
918
  if outfile == None:
919
919
  return pickle.dumps(dict_obj)
920
- outfile = Path(outfile).stem + ".pickle"
920
+ outfile = Path(outfile).with_suffix(".pickle")
921
921
  with open(outfile, "wb") as f:
922
922
  pickle.dump(dict_obj, f)
923
923
  if format == "json":
924
924
  if outfile == None:
925
925
  return json.dumps(dict_obj, cls=EncodeFromNumpy, indent=indent)
926
- outfile = Path(outfile).stem + ".json"
926
+ outfile = Path(outfile).with_suffix(".json")
927
927
  with open(outfile, "w") as f:
928
928
  json.dump(dict_obj, f, cls=EncodeFromNumpy, indent=indent)
929
929
  return None
@@ -14,6 +14,7 @@ from contextlib import redirect_stdout
14
14
  from io import StringIO
15
15
  from itertools import permutations
16
16
  from contextlib import suppress
17
+ from collections import namedtuple
17
18
 
18
19
  import numpy as np
19
20
  from pandas.io.clipboard import clipboard_get, clipboard_set
@@ -630,7 +631,7 @@ class POSCAR:
630
631
  return self._cell
631
632
 
632
633
  def get_plane(self, hkl, d=1/2,tol=1e-2):
633
- """Returns Nx3 vertices of a plane bound inside cell. .
634
+ """Returns tuple `Plane(point, normal, vertices)` of a plane bound inside cell. .
634
635
  hkl should be list of three miller indices. d is fractional distance in range 0,1 in direction of hkl.
635
636
  e.g. if there are 8 planes of atoms in a cubic cell, d = 0, 1/8,...7/8, 1 match position of those planes.
636
637
  """
@@ -651,13 +652,13 @@ class POSCAR:
651
652
  qts = self.cell.to_fractional(pts)
652
653
  qts = qts[(qts >= -tol).all(axis=1) & (qts <= 1 + tol).all(axis=1)]
653
654
  pts = self.cell.to_cartesian(qts)
654
- return pts
655
+ return namedtuple("Plane","point normal vertices")(point, normal, pts)
655
656
 
656
657
  def splot_plane(self, hkl, d=1/2,tol=1e-2,ax=None, **kwargs):
657
658
  """Provide hkl and a 3D axes to plot plane. kwargs are passed to `mpl_toolkits.mplot3d.art3d.Poly3DCollection`
658
659
  Note: You may get wrong plane if your basis are not aligned to axes. So you can use `transpose` or `set_zdir` methods before plottling cell.
659
660
  """
660
- P = self.get_plane(hkl,d=d,tol=tol)
661
+ P = self.get_plane(hkl,d=d,tol=tol).vertices
661
662
  if ax is None:
662
663
  ax = get_axes(axes_3d=True)
663
664
  ax.set( # it does not show otherwise
@@ -676,9 +677,9 @@ class POSCAR:
676
677
  fig = go.Figure()
677
678
 
678
679
  P = self.get_plane(hkl,d=d,tol=tol)
679
- kwargs['delaunayaxis'] = ('xyz')[np.eye(3).dot(hkl).argmax()] # with alphahull=-1, delaunayaxis to be set properly
680
+ kwargs['delaunayaxis'] = ('xyz')[np.abs(np.eye(3).dot(P.normal)).argmax()] # with alphahull=-1, delaunayaxis to be set properly
680
681
  kwargs = {**dict(color='#8a8',opacity=0.7,alphahull=-1, showlegend=True,name=str(hkl)),**kwargs}
681
- fig.add_trace(go.Mesh3d({k:v for v,k in zip(P.T, 'xyz')},**kwargs))
682
+ fig.add_trace(go.Mesh3d({k:v for v,k in zip(P.vertices.T, 'xyz')},**kwargs))
682
683
  return fig
683
684
 
684
685
  @_sub_doc(stk.get_bz, {"basis :.*loop :": "loop :"})
@@ -70,10 +70,8 @@ class OUTCAR:
70
70
  def path(self):
71
71
  return self._path
72
72
 
73
- @_sub_doc(vp.Vasprun.read)
74
- @_sig_kwargs(vp.Vasprun.read, ("self",))
75
- def read(self, start_match, stop_match, **kwargs):
76
- return vp.Vasprun.read(
77
- self, start_match, stop_match, **kwargs
78
- ) # Pass all the arguments to the function
73
+ @_sub_doc(vp.read)
74
+ def read(self, start_match, stop_match=r'\n', nth_match=1, skip_last=False,apply=None):
75
+ kws = {k:v for k,v in locals().items() if k !='self'}
76
+ return vp.read(self.path, **kws) # Pass all the arguments to the function
79
77
 
@@ -26,8 +26,8 @@ import matplotlib.pyplot as plt
26
26
  def get_file_size(path: str):
27
27
  """Return file size"""
28
28
  if (p := Path(path)).is_file():
29
- size = p.stat.st_size
30
- for unit in ["Bytes", "KB", "MB", "GB", "TB"]:
29
+ size = p.stat().st_size
30
+ for unit in ["B", "KB", "MB", "GB", "TB"]:
31
31
  if size < 1024.0:
32
32
  return "%3.2f %s" % (size, unit)
33
33
  size /= 1024.0
@@ -267,7 +267,7 @@ def prevent_overwrite(path) -> Path:
267
267
  if out_path.exists():
268
268
  # Check existing files
269
269
  i = 0
270
- name = out_path.stem + "-{}" + out_path.suffix
270
+ name = (out_path.parent / out_path.stem) + "-{}" + out_path.suffix
271
271
  while Path(name.format(i)).is_file():
272
272
  i += 1
273
273
  out_path = Path(name.format(i))
@@ -13,6 +13,7 @@ from time import time
13
13
  from pathlib import Path
14
14
  from collections.abc import Iterable
15
15
  from functools import partial
16
+ from pprint import pformat
16
17
 
17
18
  # Widgets Imports
18
19
  from IPython.display import display
@@ -39,7 +40,7 @@ import plotly.graph_objects as go
39
40
  from . import utils as gu
40
41
  from . import lattice as lat
41
42
  from .core import serializer, parser as vp, plot_toolkit as ptk
42
- from .utils import _sig_kwargs, _sub_doc
43
+ from .utils import _sig_kwargs, _sub_doc, get_file_size
43
44
  from ._enplots import _fmt_labels
44
45
 
45
46
 
@@ -67,7 +68,7 @@ def summarize(files, func, **kwargs):
67
68
  for name, path in files.items():
68
69
  output = func(path, **kwargs)
69
70
  if not isinstance(output, dict):
70
- raise TypeError("Function must return a dictionary.")
71
+ raise TypeError("Function must return a dictionary to create DataFrame.")
71
72
 
72
73
  if "FILE" in output:
73
74
  raise KeyError(
@@ -114,6 +115,8 @@ class Files:
114
115
  Use methods on return such as `summarize`, `with_name`, `filtered`, `interact` and others.
115
116
 
116
117
  >>> Files(root_1, glob_1,...).add(root_2, glob_2,...) # Fully flexible to chain
118
+
119
+ WARNING: Don't use write operations on paths in files in batch mode, it can cause unrecoverable data loss.
117
120
  """
118
121
  def __init__(self, path_or_files = '.', glob = '*', exclude = None,files_only = False, dirs_only=False):
119
122
  if isinstance(path_or_files, Files):
@@ -167,10 +170,6 @@ class Files:
167
170
  def __add__(self, other):
168
171
  raise NotImplementedError("Use self.add method instead!")
169
172
 
170
- def map(self,func):
171
- "Map files to a function!"
172
- return map(func, self._files)
173
-
174
173
  def with_name(self, name):
175
174
  "Change name of all files. Only keeps existing files."
176
175
  return self.__class__([f.with_name(name) for f in self._files])
@@ -262,10 +261,10 @@ class Files:
262
261
  If you don't need to interpret the result of the function call, you can use the @self.interact decorator instead.
263
262
  """
264
263
  info = ipw.HTML().add_class("fprogess")
265
- dd = Dropdown(description='File', options=self._files)
264
+ dd = Dropdown(description='File', options=['Select a File',*self._files]) # allows single file workable
266
265
 
267
266
  def interact_func(fname, **kws):
268
- if fname: # This would be None if no file is selected
267
+ if fname and str(fname) != 'Select a File': # This would be None if no file is selected
269
268
  info.value = _progress_svg
270
269
  try:
271
270
  start = time()
@@ -419,6 +418,52 @@ class Files:
419
418
  def bands_widget(self, height='450px'):
420
419
  "Get BandsWidget instance with these files."
421
420
  return BandsWidget(files=self._files, height=height)
421
+
422
+ def map(self,func, to_df=False):
423
+ """Map files to a function that takes path as argument.
424
+ If `to_df=True`, func may return a dict to create named columns, or just two columns will be created.
425
+ Otherwise returns generator of elemnets `(path, func(path))`.
426
+ If you need to operate on opened file pointer, use `.mapf` instead.
427
+
428
+ >>> import ipyvasp as ipv
429
+ >>> files = ipv.Files(...)
430
+ >>> files.map(lambda path: ipv.read(path, '<pattern>',apply = lambda line: float(line.split()[0])))
431
+ >>> files.map(lambda path: ipv.load(path), to_df=True)
432
+ """
433
+ if to_df:
434
+ return self._try_return_df(func)
435
+ return ((path, func(path)) for path in self._files) # generator must
436
+
437
+ def _try_return_df(self, func):
438
+ try: return summarize(self._files,func)
439
+ except: return pd.DataFrame(((path, func(path)) for path in self._files))
440
+
441
+ def mapf(self, func, to_df=False,mode='r', encoding=None):
442
+ """Map files to a function that takes opened file pointer as argument. Opened files are automatically closed and should be in readonly mode.
443
+ Load files content into a generator sequence of tuples like `(path, func(open(path)))` or DataFrame if `to_df=True`.
444
+ If `to_df=True`, func may return a dict to create named columns, or just two columns will be created.
445
+ If you need to operate on just path, use `.map` instead.
446
+
447
+ >>> import json
448
+ >>> import ipyvasp as ipv
449
+ >>> files = ipv.Files(...)
450
+ >>> files.mapf(lambda fp: json.load(fp),to_df=True)
451
+ >>> files.mapf(lambda fp: [fp.readline() for _ in range(5)]) # read first five lines
452
+ """
453
+ if not mode in 'rb':
454
+ raise ValueError("Only 'r'/'rb' mode is allowed in this context!")
455
+
456
+ def loader(path):
457
+ with open(path, mode=mode,encoding=encoding) as f:
458
+ return func(f)
459
+
460
+ if to_df:
461
+ return self._try_return_df(loader)
462
+ return ((path, loader(path)) for path in self._files) # generator must
463
+
464
+ def stat(self):
465
+ "Get files stat as DataFrame. Currently only size is supported."
466
+ return self.summarize(lambda path: {"size": get_file_size(path)})
422
467
 
423
468
 
424
469
  @fix_signature
@@ -627,7 +672,7 @@ class BandsWidget(VBox):
627
672
  self._tsd = Dropdown(
628
673
  description="Style", options=["plotly_white", "plotly_dark"]
629
674
  )
630
- self._click = Dropdown(description="Click", options=["None", "VBM", "CBM"])
675
+ self._click = Dropdown(description="Click", options=["None", "vbm", "cbm"])
631
676
  self._ktcicks = Text(description="kticks")
632
677
  self._brange = ipw.IntRangeSlider(description="bands",min=1, max=1) # number, not index
633
678
  self._ppicks = PropsPicker(
@@ -680,9 +725,9 @@ class BandsWidget(VBox):
680
725
  )
681
726
  self._brange.max = self.bands.source.summary.NBANDS
682
727
  if self.bands.source.summary.LSORBIT:
683
- self._click.options = ["None", "VBM", "CBM", "so_max", "so_min"]
728
+ self._click.options = ["None", "vbm", "cbm", "so_max", "so_min"]
684
729
  else:
685
- self._click.options = ["None", "VBM", "CBM"]
730
+ self._click.options = ["None", "vbm", "cbm"]
686
731
 
687
732
  if (file := path.parent / "result.json").is_file():
688
733
  self._result = serializer.load(str(file.absolute())) # Old data loaded
@@ -692,7 +737,7 @@ class BandsWidget(VBox):
692
737
  {
693
738
  "v": round(pdata.volume, 4),
694
739
  **{k: round(v, 4) for k, v in zip("abc", pdata.norms)},
695
- **{k: round(v, 4) for k, v in zip("αβγ", pdata.angles)},
740
+ **{k: round(v, 4) for k, v in zip(["alpha","beta","gamma"], pdata.angles)},
696
741
  }
697
742
  )
698
743
  self._click_save_data(None) # Load into view
@@ -771,13 +816,10 @@ class BandsWidget(VBox):
771
816
  def _show_and_save(data_dict):
772
817
  self._interact.output_widget.clear_output(wait=True) # Why need again?
773
818
  with self._interact.output_widget:
774
- print(
775
- ", ".join(
776
- f"{key} = {value}"
819
+ print(pformat({key: value
777
820
  for key, value in data_dict.items()
778
821
  if key not in ("so_max", "so_min")
779
- )
780
- )
822
+ }))
781
823
 
782
824
  serializer.dump(
783
825
  data_dict,
@@ -785,11 +827,9 @@ class BandsWidget(VBox):
785
827
  outfile=self.path.parent / "result.json",
786
828
  )
787
829
 
788
- if (
789
- change is None
790
- ): # called from other functions but not from store_clicked_data
830
+ if change is None: # called from other functions but not from store_clicked_data
791
831
  return _show_and_save(self._result)
792
- # Should be after checking chnage
832
+ # Should be after checking change
793
833
  if self._click.value and self._click.value == "None":
794
834
  return # No need to act on None
795
835
 
@@ -806,11 +846,11 @@ class BandsWidget(VBox):
806
846
  x, 6
807
847
  ) # Save x to test direct/indirect
808
848
 
809
- if data_dict.get("VBM", None) and data_dict.get("CBM", None):
810
- data_dict["E_gap"] = np.round(data_dict["CBM"] - data_dict["VBM"], 6)
849
+ if data_dict.get("vbm", None) and data_dict.get("cbm", None):
850
+ data_dict["gap"] = np.round(data_dict["cbm"] - data_dict["vbm"], 6)
811
851
 
812
852
  if data_dict.get("so_max", None) and data_dict.get("so_min", None):
813
- data_dict["Δ_SO"] = np.round(
853
+ data_dict["soc"] = np.round(
814
854
  data_dict["so_max"] - data_dict["so_min"], 6
815
855
  )
816
856
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipyvasp
3
- Version: 0.9.83
3
+ Version: 0.9.85
4
4
  Summary: A processing tool for VASP DFT input/output processing in Jupyter Notebook.
5
5
  Home-page: https://github.com/massgh/ipyvasp
6
6
  Author: Abdul Saboor
@@ -1 +0,0 @@
1
- __version__ = "0.9.83"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes