ipyvasp 0.9.2__py2.py3-none-any.whl → 0.9.3__py2.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.
ipyvasp/__init__.py CHANGED
@@ -6,6 +6,8 @@ widgets for interactive visualization and bulk analysis.
6
6
 
7
7
 
8
8
  __all__ = [ # For documentation purpose
9
+ "Files",
10
+ "OUTCAR",
9
11
  "get_axes",
10
12
  "plt2text",
11
13
  "plt2html",
@@ -14,11 +16,9 @@ __all__ = [ # For documentation purpose
14
16
  "image2plt",
15
17
  "iplot2plt",
16
18
  "webshow",
17
- "list_files",
18
19
  "load_results",
19
20
  "parse_text",
20
21
  "summarize",
21
- "OUTCAR",
22
22
  ]
23
23
 
24
24
  from ._version import __version__
@@ -30,7 +30,7 @@ from .bsdos import *
30
30
  from .potential import *
31
31
  from .evals_dataframe import *
32
32
  from .utils import *
33
- from .widgets import BandsWidget, KpathWidget, FilesWidget, summarize, load_results
33
+ from .widgets import Files, BandsWidget, KpathWidget, summarize, load_results
34
34
  from .core import plot_toolkit, spatial_toolkit
35
35
  from .core.spatial_toolkit import to_basis, to_R3, get_TM, get_bz, rotation
36
36
  from .core.plot_toolkit import (
ipyvasp/_lattice.py CHANGED
@@ -11,7 +11,8 @@ from scipy.spatial import ConvexHull, KDTree
11
11
  import plotly.graph_objects as go
12
12
 
13
13
  import matplotlib.pyplot as plt # For viewpoint
14
- from matplotlib.collections import LineCollection
14
+ from matplotlib.collections import LineCollection, PatchCollection
15
+ from matplotlib.patches import Rectangle
15
16
  from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
16
17
  import matplotlib.colors as mplc
17
18
 
@@ -171,17 +172,25 @@ def atoms_color():
171
172
  )
172
173
 
173
174
 
174
- def periodic_table():
175
- "Display colorerd elements in periodic table."
175
+ def periodic_table(selection=None):
176
+ "Display colorerd elements in periodic table. Use a list of atoms to only color a selection."
176
177
  _copy_names = np.array(
177
178
  [f"$^{{{str(i+1)}}}${k}" for i, k in enumerate(_atom_colors.keys())]
178
179
  )
179
- _copy_array = np.array(list(_atom_colors.values()))
180
+ blank = []
181
+ if isinstance(selection,(list, tuple, str)):
182
+ if isinstance(selection, str):
183
+ selection = selection.split()
184
+ blank = [key for key in _atom_colors if not (key in selection)]
185
+
186
+ _copy_array = np.array([[1,1,1,0] if key in blank else [*value,1] for key, value in _atom_colors.items()])
180
187
 
181
- array = np.ones((180, 3))
182
188
  names = ["" for i in range(180)] # keep as list before modification
189
+ fc = np.ones((180, 4))
190
+ ec = np.zeros((180,3)) + (0.4 if blank else 0.9 )
191
+ offsets = np.array([[(i,j) for i in range(18)] for j in range(10)]).reshape((-1,2)) - 0.5
183
192
 
184
- inds = [
193
+ inds = np.array([
185
194
  (0, 0),
186
195
  (17, 1),
187
196
  (18, 2),
@@ -196,23 +205,29 @@ def periodic_table():
196
205
  *[(111 + i, 103 + i) for i in range(15)],
197
206
  *[(147 + i, 57 + i) for i in range(14)],
198
207
  *[(165 + i, 89 + i) for i in range(14)],
199
- ]
208
+ ], dtype=int)
200
209
 
201
210
  for i, j in inds:
202
- array[i] = _copy_array[j]
211
+ fc[i,:] = _copy_array[j]
203
212
  names[i] = _copy_names[j]
204
213
 
205
- array = np.reshape(array, (10, 18, 3))
206
- names = np.reshape(names, (10, 18))
207
- ax = ptk.get_axes((8, 4.5))
208
- ax.imshow(array)
214
+ fidx = [i for i, _ in inds] # only plot at elements posistions,otherwise they overlap
215
+ offsets = offsets[fidx]
216
+ fc, ec = fc[fidx], ec[fidx]
217
+ names = np.array(names)[fidx]
218
+
219
+ # We are adding patches, because imshow does not properly appear in PDF of latex
220
+ ax = ptk.get_axes((7, 3.9),left=0.01,right=0.99,top=0.99,bottom=0.01)
221
+ patches = np.array([Rectangle(offset,0.9 if i in [92,110] else 1,1) for i, offset in zip(fidx,offsets)])
222
+ pc = PatchCollection(patches, facecolors=fc, edgecolors=ec,linewidths=(0.7,))
223
+ ax.add_collection(pc)
224
+
225
+ for (x,y), text, c in zip(offsets + 0.5, names, fc):
226
+ c = "k" if np.linalg.norm(c[:3]) > 1 else "w"
227
+ plt.text(x,y, text, color=c, ha="center", va="center")
209
228
 
210
- for i in range(18):
211
- for j in range(10):
212
- c = "k" if np.linalg.norm(array[j, i]) > 1 else "w"
213
- plt.text(i, j, names[j, i], color=c, ha="center", va="center")
214
229
  ax.set_axis_off()
215
- plt.tight_layout(pad=0.5)
230
+ ax.set(xlim=[-0.6,17.6],ylim=[9.6,-0.6]) # to show borders correctly
216
231
  return ax
217
232
 
218
233
 
@@ -299,7 +314,7 @@ def export_poscar(path=None, content=None):
299
314
  SYSTEM = header[0].strip()
300
315
  comment = header[1].strip() if len(header) > 1 else "Exported by Pivopty"
301
316
 
302
- scale = float(file_lines[1].strip())
317
+ scale = float(file_lines[1].strip().split()[0]) # some people add comments here too
303
318
  if scale < 0: # If that is for volume
304
319
  scale = 1
305
320
 
ipyvasp/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.9.2"
1
+ __version__ = "0.9.3"
ipyvasp/bsdos.py CHANGED
@@ -578,13 +578,7 @@ class Bands(_BandsDosBase):
578
578
 
579
579
  def view_bands(self, height="450px"):
580
580
  "Initialize and return `ipyvasp.widgets.BandsWidget` to view bandstructure interactively."
581
- use_vaspout = True if isinstance(self.source, vp.Vaspout) else False
582
- return wdg.BandsWidget(
583
- use_vaspout=use_vaspout,
584
- path=str(self.source.path.parent),
585
- glob=self.source.path.name,
586
- height=height,
587
- )
581
+ return wdg.BandsWidget([self.source.path,],height=height)
588
582
 
589
583
 
590
584
  _multiply_doc = """multiply : float
ipyvasp/lattice.py CHANGED
@@ -432,11 +432,11 @@ class POSCAR:
432
432
  def view_weas(self, **kwargs):
433
433
  return weas_viewer(self, **kwargs)
434
434
 
435
- def view_kpath(self):
435
+ def view_kpath(self, height='400px'):
436
436
  "Initialize a KpathWidget instance to view kpath for current POSCAR, and you can select others too."
437
437
  from .widgets import KpathWidget
438
438
 
439
- return KpathWidget(path=str(self.path.parent), glob=self.path.name)
439
+ return KpathWidget([self.path,],height=height)
440
440
 
441
441
  @_sub_doc(plat.iplot_lattice)
442
442
  @_sig_kwargs(plat.iplot_lattice, ("poscar_data",))
ipyvasp/utils.py CHANGED
@@ -3,7 +3,6 @@ __all__ = [
3
3
  "set_dir",
4
4
  "interpolate_data",
5
5
  "rolling_mean",
6
- "list_files",
7
6
  "color",
8
7
  "transform_color",
9
8
  "create_colormap",
@@ -257,37 +256,6 @@ def rolling_mean(
257
256
 
258
257
  return mean_all
259
258
 
260
-
261
- def list_files(path=".", glob="*", exclude=None, files_only=False, dirs_only=False):
262
- """
263
- Returns a tuple of files in a directory recursively based on glob pattern.
264
-
265
- Parameters
266
- ----------
267
- path : str, current directory by default
268
- glob : str, glob pattern, '*' by default
269
- exclude : str, regular expression pattern to exclude files
270
- files_only : bool, if True, returns only files
271
- dirs_only : bool, if True, returns only directories
272
-
273
- Returns
274
- -------
275
- tuple of pathlib.Path objects
276
- """
277
- if files_only and dirs_only:
278
- raise ValueError("files_only and dirs_only cannot be both True")
279
-
280
- path = Path(path)
281
- files = [p for p in path.glob(glob)]
282
- if exclude:
283
- files = [p for p in files if not re.search(exclude, str(p))]
284
- if files_only:
285
- files = [p for p in files if p.is_file()]
286
- if dirs_only:
287
- files = [p for p in files if p.is_dir()]
288
- return tuple(sorted(files)) # sorting is important here
289
-
290
-
291
259
  @contextmanager
292
260
  def prevent_overwrite(path) -> Path:
293
261
  """Contextmanager to prevents overwiting as file by adding numbers in given path.
ipyvasp/widgets.py CHANGED
@@ -1,14 +1,14 @@
1
1
  __all__ = [
2
2
  "load_results",
3
3
  "summarize",
4
- "FilesWidget",
4
+ "Files",
5
5
  "PropsPicker",
6
6
  "BandsWidget",
7
7
  "KpathWidget",
8
8
  ]
9
9
 
10
10
 
11
- import inspect
11
+ import inspect, re
12
12
  from time import time
13
13
  from pathlib import Path
14
14
  from collections.abc import Iterable
@@ -57,8 +57,8 @@ def summarize(files, func, **kwargs):
57
57
  if not callable(func):
58
58
  raise TypeError("Argument `func` must be a function.")
59
59
 
60
- if not isinstance(files, Iterable):
61
- raise TypeError("Argument `files` must be an iterable of PathLike objects.")
60
+ if not isinstance(files, Iterable): # Files is instance of Iterable due to __iter__ method
61
+ raise TypeError("Argument `files` must be an iterable of PathLike objects")
62
62
 
63
63
  if not isinstance(files, dict):
64
64
  files = {str(path): path for path in files} # make a dictionary of paths
@@ -78,15 +78,12 @@ def summarize(files, func, **kwargs):
78
78
  {**output, "FILE": name}
79
79
  ) # add the file name to the output at the end
80
80
 
81
- unique_keys = (
82
- []
83
- ) # get all unique keys, there would be missing or extra keys, handle all
84
- for key in [key for out in outputs for key in out.keys()]:
85
- if key not in unique_keys:
86
- unique_keys.append(key)
81
+ unique_keys = {} # handle missing keys with types
82
+ for key,value in [item for out in outputs for item in out.items()]:
83
+ unique_keys[key] = '' if isinstance(value, str) else None
87
84
 
88
85
  return pd.DataFrame(
89
- {ukey: [out.get(ukey, None) for out in outputs] for ukey in unique_keys}
86
+ {key: [out.get(key, ph) for out in outputs] for key,ph in unique_keys.items()}
90
87
  )
91
88
 
92
89
 
@@ -102,236 +99,171 @@ def fix_signature(cls):
102
99
  cls.__signature__ = inspect.signature(cls.__init__)
103
100
  return cls
104
101
 
105
-
106
- @fix_signature
107
- class FilesWidget(VBox):
108
- """A widget for selecting files from a directory and its subdirectories.
102
+ class Files:
103
+ """Creates a Batch of files in a directory recursively based on glob pattern or given list of files.
104
+ This is a boilerplate abstraction to do analysis in multiple calculations simultaneously.
109
105
 
110
106
  Parameters
111
107
  ----------
112
- path : str, default is '.'. The path to the directory to search.
113
- glob : str, default is '*'. The glob pattern to match files against. See https://docs.python.org/3/library/glob.html
114
- exclude : str, default is None. A regex pattern to exclude files from the selection.
115
- on_file_changed : callable, default is None.
116
- A function that takes path as an argument and is called when the selected file changes. Output is displayed below the widget.
117
- To add extra controls and widgets, use `.interactive`/`.interact` methods instead.
118
-
119
- Returns
120
- -------
121
- A FilesWidget object where you can filter files by typing in the text box, and select files from the dropdown.
122
- """
123
-
124
- def __init__(
125
- self,
126
- path: str = ".",
127
- glob: str = "*",
128
- exclude: str = None,
129
- on_file_changed=None,
130
- ) -> None:
131
- for prop in (path, glob, exclude):
132
- if prop and not isinstance(prop, str):
133
- raise ValueError(f"Expected string, got {type(prop)}")
134
-
135
- super().__init__(_dom_classes=["FilesWidget"]) # This makes it truely a widget
136
- self._files = [] # Selections stored as Path objects
137
- self._widgets = {
138
- "input": Text(
139
- value=path,
140
- description="Path:",
141
- tooltip="The path to the directory to search.",
142
- ),
143
- "glob": Text(
144
- value=glob,
145
- description="Glob:",
146
- tooltip="The glob pattern to match files against. See https://docs.python.org/3/library/glob.html",
147
- ),
148
- "exclude": Text(
149
- value=exclude or "",
150
- description="Exclude:",
151
- tooltip="A regex pattern to exclude files from the selection.",
152
- ),
153
- "lock": Checkbox(
154
- value=False,
155
- description="Lock selection",
156
- tooltip="Lock the current selection and prevent changes.",
157
- ),
158
- "files": Dropdown(
159
- options=[], description="File:", tooltip="Select a file from the list."
160
- ),
161
- }
162
- self.children = [
163
- self._widgets["input"],
164
- self._widgets["glob"],
165
- self._widgets["exclude"],
166
- ]
167
- self._widgets["lock"].observe(self._lock_selection, names=["value"])
168
-
169
- for key, value in self._widgets.items():
170
- if key not in ["files", "lock"]:
171
- value.on_submit(self._process)
172
-
173
- if on_file_changed:
174
- if not callable(on_file_changed):
175
- raise TypeError(
176
- "Argument `on_file_changed` must be a function that takes path as an argument."
177
- )
178
-
179
- out = ipw.Output()
180
- self.layout.max_height = "90vh" # Only if output is present
181
- self._widgets["output"] = out
108
+ path_or_files : str, current directory by default or list of files or an instance of Files.
109
+ glob : str, glob pattern, '*' by default. Not used if files supplied above.
110
+ exclude : str, regular expression pattern to exclude files.
111
+ files_only : bool, if True, returns only files.
112
+ dirs_only : bool, if True, returns only directories.
182
113
 
183
- @out.capture(clear_output=True, wait=True)
184
- def on_change(change):
185
- on_file_changed(self.selected)
186
-
187
- self._widgets["files"].observe(on_change, names=["value"])
188
-
189
- self._process(None) # Initial processing based on given values
190
-
191
- def _lock_selection(self, change):
192
- for key, value in self._widgets.items():
193
- if key not in ["files", "lock"]:
194
- value.disabled = self._widgets[
195
- "lock"
196
- ].value # Don't allow changes even programatically
197
-
198
- if self._widgets["lock"].value:
199
- self._widgets["lock"].description = f"{len(self._files)} files selected"
200
- self.children = [self._widgets["lock"], self._widgets["files"]]
114
+ Use methods on return such as `summarize`, `with_name`, `filtered`, `interact` and others.
115
+ """
116
+ def __init__(self, path_or_files = '.', glob = '*', exclude = None,files_only = False, dirs_only=False):
117
+ if isinstance(path_or_files, Files):
118
+ self._files = path_or_files._files
119
+ return # Do nothing
120
+
121
+ if files_only and dirs_only:
122
+ raise ValueError("files_only and dirs_only cannot be both True")
123
+
124
+ files = []
125
+ if isinstance(path_or_files,(str, Path)):
126
+ path = Path(path_or_files)
127
+ files = [p for p in path.glob(glob)]
201
128
  else:
202
- self.children = [
203
- self._widgets["input"],
204
- self._widgets["glob"],
205
- self._widgets["exclude"],
206
- ]
207
-
208
- if self._widgets.get("output"):
209
- self.children = [*self.children, self._widgets["output"]]
210
-
211
- def _process(self, change):
212
- self._widgets["lock"].description = "Processing..."
213
- files = gu.list_files(
214
- self._widgets["input"].value,
215
- glob=self._widgets["glob"].value,
216
- exclude=self._widgets["exclude"].value,
217
- )
218
-
219
- self._widgets["files"].options = [
220
- str(p) for p in files
221
- ] # shows only relative path
222
- self._files = [path.absolute() for path in files] # Store as full path
223
- self.children = list(self._widgets.values()) # show all widgets
224
- self._widgets[
225
- "lock"
226
- ].description = f"{len(self._files)} files found. Lock selection?"
227
-
228
- @property
229
- def paths(self):
230
- "Returns all availble paths."
231
- return tuple(self._files)
232
-
233
- @property
234
- def dropdown(self):
235
- "Returns the dropdown widget to select files."
236
- return self._widgets["files"]
237
-
238
- @property
239
- def selected(self) -> Path:
240
- "Returns selected item in the dropdown as Path object."
241
- if self._widgets["files"].value: # if not empty, otherwise it throws error
242
- return Path(self._widgets["files"].value).absolute() # return full path
243
-
244
- @property
245
- def path(self): # This is in consistent with other widgets too
246
- "Return currently selected path."
247
- return self.selected
129
+ others = []
130
+ for item in path_or_files:
131
+ if isinstance(item, str):
132
+ item = Path(item)
133
+ elif not isinstance(item, Path):
134
+ raise TypeError(f"Expected str or Path in sequence, got {type(item)}")
135
+
136
+ if item.exists():
137
+ files.append(item)
138
+ else:
139
+ others.append(str(item))
140
+
141
+ if others:
142
+ print(f"Skipping paths that do not exist: {list(set(others))}")
143
+
144
+ if exclude:
145
+ files = [p for p in files if not re.search(exclude, str(p))]
146
+ if files_only:
147
+ files = [p for p in files if p.is_file()]
148
+ if dirs_only:
149
+ files = [p for p in files if p.is_dir()]
150
+
151
+ self._files = tuple(sorted(files))
152
+
153
+ def __repr__(self):
154
+ if not self: return "Files()"
155
+ return "Files(\n" + ',\n'.join(f' {f!r}' for f in self._files) + "\n)"
156
+
157
+ def __getitem__(self, index): return self._files[index]
158
+ def __iter__(self): return self._files.__iter__()
159
+ def __len__(self): return len(self._files)
160
+ def __bool__(self): return bool(self._files)
161
+
162
+ def with_name(self, name):
163
+ "Change name of all files. Only keeps existing files."
164
+ return self.__class__([f.with_name(name) for f in self._files])
165
+
166
+ def filtered(self, include=None, exclude=None, files_only = False, dirs_only=False):
167
+ "Filter all files. Only keeps existing file."
168
+ files = [p for p in self._files if re.search(include, str(p))] if include else self._files
169
+ return self.__class__(files, exclude=exclude,dirs_only=dirs_only,files_only=files_only)
248
170
 
249
- def interactive(
250
- self,
251
- func,
171
+ def summarize(self, func, **kwargs):
172
+ "Apply a func(apth) -> dict and create a dataframe."
173
+ return summarize(self._files,func, **kwargs)
174
+
175
+ def load_results(self):
176
+ "Load result.json files from these paths into a dataframe."
177
+ return load_results(self._files)
178
+
179
+ def input_info(self, *tags):
180
+ "Grab input information into a dataframe from POSCAR and INCAR. Provide INCAR tags (case-insinsitive) to select only few of them."
181
+ from .lattice import POSCAR
182
+
183
+ def info(path, tags):
184
+ p = POSCAR(path).data
185
+ lines = [[v.strip() for v in line.split('=')]
186
+ for line in path.with_name('INCAR').read_text().splitlines()
187
+ if '=' in line]
188
+ if tags:
189
+ tags = [t.upper() for t in tags] # can send lowercase tag
190
+ lines = [(k,v) for k,v in lines if k in tags]
191
+ d = {k:v for k,v in lines if not k.startswith('#')}
192
+ d.update({k:len(v) for k,v in p.types.items()})
193
+ d.update(zip('abcvαβγ', [*p.norms,p.volume,*p.angles]))
194
+ return d
195
+
196
+ return self.with_name('POSCAR').summarize(info, tags=tags)
197
+
198
+ def update(self, path_or_files, glob = '*',**kwargs):
199
+ "Update files inplace with similar parameters as initialization. Useful for widgets such as BandsWidget to preserve their state while files swapping."
200
+ self._files = self.__class__(path_or_files, glob = glob, **kwargs)
201
+ if (dd := getattr(self, '_dd', None)): # update dropdown
202
+ old = dd.value
203
+ dd.options = self._files
204
+ if old in dd.options:
205
+ dd.value = old
206
+
207
+ def interactive(self, func,
252
208
  other_widgets=None,
253
209
  other_controls=None,
254
210
  options={"manual": False},
255
211
  height="400px",
256
- **kwargs,
257
- ):
212
+ **kwargs):
258
213
  """
259
214
  Interact with a function that takes selected Path as first argument. Returns a widget that saves attributes of the function call such as .f, .args, .kwargs.
260
215
  See docs of self.interact for more details on the parameters. kwargs are passed to ipywidgets.interactive to create controls.
261
216
 
262
217
 
263
- >>> fw = FilesWidget()
264
- >>> out = fw.interactive(lambda path: print(path.read_text())) # prints contents of selected file on output widget
218
+ >>> fls = Files()
219
+ >>> out = fls.interactive(lambda path: print(path.read_text())) # prints contents of selected file on output widget
265
220
  >>> out.f # function
266
221
  >>> out.args # arguments
267
222
  >>> out.kwargs # keyword arguments
268
223
  >>> out.result # result of function call which is same as out.f(*out.args, **out.kwargs)
269
- >>> out.files_widget # reference to FilesWidget created, not the same as fw because it is a new instance
270
-
271
224
 
272
225
  .. note::
273
226
  If you don't need to interpret the result of the function call, you can use the @self.interact decorator instead.
274
-
275
- .. note::
276
- Each time an underlying new FilesWidget instance is created which picks input from previous one but stays separate. You can access it with `.files_widget` attribute of interactive.
277
227
  """
278
- # Make new FilesWidget with same parameters, to allow multiple interact calls
279
- new_fw = self.__class__(
280
- path=self._widgets["input"].value,
281
- glob=self._widgets["glob"].value,
282
- exclude=self._widgets["exclude"].value,
283
- )
284
- info = ipw.HTML().add_class("FW-Progess")
228
+ info = ipw.HTML().add_class("fprogess")
229
+ dd = Dropdown(description='File', options=self._files)
285
230
 
286
231
  def interact_func(fname, **kwargs):
287
232
  if fname: # This would be None if no file is selected
288
233
  info.value = _progress_svg
289
234
  try:
290
235
  start = time()
291
- print(
292
- f"Running {func.__name__}({fname!r}, {kwargs})"
293
- ) # it also serves as removing the output errors
294
- func(
295
- Path(fname).absolute(), **kwargs
296
- ) # Have Path object absolue if user changes directory
236
+ print(f"Running {func.__name__}({fname!r}, {kwargs})")
237
+ func(Path(fname).absolute(), **kwargs) # Have Path object absolue if user changes directory
297
238
  print(f"Finished in {time() - start:.3f} seconds.")
298
239
  finally:
299
240
  info.value = ""
300
241
 
301
- out = ipw.interactive(
302
- interact_func, options, fname=new_fw._widgets["files"], **kwargs
303
- )
304
-
305
- out.files_widget = new_fw # save reference to FilesWidget
306
- out.output_widget = out.children[
307
- -1
308
- ] # save reference to output widget for other widgets to use
242
+ out = ipw.interactive(interact_func, options, fname=dd, **kwargs)
243
+ out._dd = dd # save reference to dropdown
244
+ out.output_widget = out.children[-1] # save reference to output widget for other widgets to use
309
245
 
310
246
  if options.get("manual", False):
311
- out.interact_button = out.children[
312
- -2
313
- ] # save reference to interact button for other widgets to use
247
+ out.interact_button = out.children[-2] # save reference to interact button for other widgets to use
314
248
 
315
249
  output = out.children[-1] # get output widget
316
- output.clear_output(
317
- wait=True
318
- ) # clear output by waiting to avoid flickering, this is important
250
+ output.clear_output(wait=True) # clear output by waiting to avoid flickering, this is important
319
251
  output.layout = Layout(
320
252
  overflow="auto", max_height="100%", width="100%"
321
253
  ) # make output scrollable and avoid overflow
322
254
 
323
255
  others = out.children[1:-1] # exclude files_dd and Output widget
324
256
  _style = """<style>
325
- .FW-Interact {
257
+ .files-interact {
326
258
  --jp-widgets-inline-label-width: 4em;
327
259
  --jp-widgets-inline-width: 18em;
328
260
  --jp-widgets-inline-width-short: 9em;
329
261
  }
330
- .FW-Interact {max-height:90vh;width:100%;}
331
- .FW-Interact > div {overflow:auto;max-height:100%;padding:8px;}
332
- .FW-Interact > div:first-child {width:20em}
333
- .FW-Interact > div:last-child {width:calc(100% - 20em)}
334
- .FW-Interact .FW-Progess {position:absolute !important; left:50%; top:50%; transform:translate(-50%,-50%); z-index:1}
262
+ .files-interact {max-height:90vh;width:100%;}
263
+ .files-interact > div {overflow:auto;max-height:100%;padding:8px;}
264
+ .files-interact > div:first-child {width:20em}
265
+ .files-interact > div:last-child {width:calc(100% - 20em)}
266
+ .files-interact .fprogess {position:absolute !important; left:50%; top:50%; transform:translate(-50%,-50%); z-index:1}
335
267
  </style>"""
336
268
  if others:
337
269
  others = [ipw.HTML(f"<hr/>{_style}"), *others]
@@ -368,19 +300,24 @@ class FilesWidget(VBox):
368
300
  HBox(
369
301
  [ # reset children to include new widgets
370
302
  VBox(
371
- children=[new_fw, VBox(others)]
303
+ children=[dd, VBox(others)]
372
304
  ), # other widgets in box to make scrollable independent file selection
373
305
  VBox(
374
306
  children=[Box([output]), *(other_widgets or []), info]
375
307
  ), # output in box to make scrollable,
376
308
  ],
377
309
  layout=Layout(height=height, max_height=height),
378
- ).add_class("FW-Interact")
310
+ ).add_class("files-interact")
379
311
  ] # important for every widget separately
380
312
  return out
381
-
382
- def interact(
383
- self,
313
+
314
+ def _attributed_interactive(self, box, func, *args, **kwargs):
315
+ box._files = self
316
+ box._interact = self.interactive(func, *args, **kwargs)
317
+ box.children = box._interact.children
318
+ box._files._dd = box._interact._dd
319
+
320
+ def interact(self,
384
321
  other_widgets=None,
385
322
  other_controls=None,
386
323
  options={"manual": False},
@@ -388,7 +325,7 @@ class FilesWidget(VBox):
388
325
  **kwargs,
389
326
  ):
390
327
  """Interact with a function that takes a selected Path as first argument.
391
- A CSS class 'FW-Interact' is added to the final widget to let you style it.
328
+ A CSS class 'files-interact' is added to the final widget to let you style it.
392
329
 
393
330
  Parameters
394
331
  ----------
@@ -402,53 +339,43 @@ class FilesWidget(VBox):
402
339
  height : str
403
340
  Default is '90vh'. height of the final widget. This is important to avoid very long widgets.
404
341
 
405
-
406
342
  kwargs are passed to ipywidgets.interactive and decorated function. Resulting widgets are placed below the file selection widget.
407
-
408
343
  `other_widgets` can be controlled by `other_controls` externally. For example, you can add a button to update a plotly's FigureWidget.
409
344
 
410
345
  The decorated function can be called later separately as well, and has .args and .kwargs attributes to access the latest arguments
411
346
  and .result method to access latest. For a function `f`, `f.result` is same as `f(*f.args, **f.kwargs)`.
412
347
 
413
348
 
414
- >>> fw = FilesWidget()
415
- >>> @fw.interact(x = False)
349
+ >>> fls = Files()
350
+ >>> @fls.interact(x = False)
416
351
  >>> def f(path,x):
417
352
  >>> print('path:',path)
418
353
  >>> print('Path Type: ', type(path))
419
354
  >>> print('x: ',x)
420
355
 
421
-
422
356
  .. note::
423
357
  Use self.interactive to get a widget that stores the argements and can be called later in a notebook cell.
424
358
  """
425
359
 
426
360
  def inner(func):
427
- display(
428
- self.interactive(
429
- func,
430
- other_widgets=other_widgets,
431
- other_controls=other_controls,
432
- options=options,
433
- height=height,
434
- **kwargs,
435
- )
361
+ display(self.interactive(func,
362
+ other_widgets=other_widgets,
363
+ other_controls=other_controls,
364
+ options=options,
365
+ height=height,
366
+ **kwargs)
436
367
  )
437
368
  return func
438
-
439
369
  return inner
370
+
440
371
 
441
- def summarize(self, func, **kwargs):
442
- """Summarize the results from all selected files using a function that takes a Path object as first arguement.
443
- kwargs are passed to function. Returns a dataframe."""
444
- return summarize(
445
- {
446
- key: value
447
- for key, value in zip(self._widgets["files"].options, self._files)
448
- },
449
- func,
450
- **kwargs,
451
- )
372
+ def kpath_widget(self, height='400px'):
373
+ "Get KpathWidget instance with these files."
374
+ return KpathWidget(files = self._files, height = height)
375
+
376
+ def bands_widget(self, height='450px'):
377
+ "Get BandsWidget instance with these files."
378
+ return BandsWidget(files=self._files, height=height)
452
379
 
453
380
 
454
381
  @fix_signature
@@ -647,12 +574,12 @@ class BandsWidget(VBox):
647
574
  Two attributes are important:
648
575
  self.clicked_data returns the last clicked point, that can also be stored as VBM, CBM etc, using Click dropdown.
649
576
  self.selected_data returns the last selection of points within a box or lasso. You can plot that output separately as plt.plot(data.xs, data.ys) after a selection.
577
+ You can use `self.files.update` method to change source files without effecting state of widget.
650
578
  """
651
579
 
652
- def __init__(self, use_vaspout=False, height="450px", **file_widget_kwargs):
580
+ def __init__(self, files, height="450px"):
653
581
  super().__init__(_dom_classes=["BandsWidget"])
654
582
  self._bands = None
655
- self._use_vaspout = use_vaspout
656
583
  self._fig = go.FigureWidget()
657
584
  self._tsd = Dropdown(
658
585
  description="Style", options=["plotly_white", "plotly_dark"]
@@ -668,11 +595,8 @@ class BandsWidget(VBox):
668
595
  self._click_dict = {} # store clicked data
669
596
  self._select_dict = {} # store selection data
670
597
  self._kwargs = {}
671
- file_widget_kwargs = {
672
- "glob": "vapout.h5" if use_vaspout else "vasprun.xml",
673
- **file_widget_kwargs,
674
- }
675
- self._interact = FilesWidget(**file_widget_kwargs).interactive(
598
+
599
+ Files(files)._attributed_interactive(self,
676
600
  self._load_data,
677
601
  other_widgets=[self._fig],
678
602
  other_controls=[
@@ -686,8 +610,7 @@ class BandsWidget(VBox):
686
610
  ],
687
611
  height=height,
688
612
  )
689
- self.files_widget = self._interact.files_widget
690
- self.children = self._interact.children
613
+
691
614
  self._tsd.observe(self._change_theme, "value")
692
615
  self._click.observe(self._click_save_data, "value")
693
616
  self._ktcicks.observe(self._warn_update, "value")
@@ -696,13 +619,18 @@ class BandsWidget(VBox):
696
619
  @property
697
620
  def path(self):
698
621
  "Returns currently selected path."
699
- return self.files_widget.path
622
+ return self._interact._dd.value
623
+
624
+ @property
625
+ def files(self):
626
+ "Use slef.files.update(...) to keep state of widget preserved."
627
+ return self._files
700
628
 
701
629
  def _load_data(self, path): # Automatically redirectes to output widget
702
630
  self._interact.output_widget.clear_output(wait=True) # Why need again?
703
631
  with self._interact.output_widget:
704
632
  self._bands = (
705
- vp.Vaspout(path) if self._use_vaspout else vp.Vasprun(path)
633
+ vp.Vasprun(path) if path.parts[-1].endswith('xml') else vp.Vaspout(path)
706
634
  ).bands
707
635
  self._ppicks.update(self.bands.source.summary)
708
636
  self._ktcicks.value = ", ".join(
@@ -714,7 +642,7 @@ class BandsWidget(VBox):
714
642
  else:
715
643
  self._click.options = ["None", "VBM", "CBM"]
716
644
 
717
- if (file := self.files_widget.selected.parent / "result.json").is_file():
645
+ if (file := path.parent / "result.json").is_file():
718
646
  self._result = serializer.load(str(file.absolute())) # Old data loaded
719
647
 
720
648
  pdata = self.bands.source.poscar.data
@@ -811,7 +739,7 @@ class BandsWidget(VBox):
811
739
  serializer.dump(
812
740
  data_dict,
813
741
  format="json",
814
- outfile=self.files_widget.selected.parent / "result.json",
742
+ outfile=self.path.parent / "result.json",
815
743
  )
816
744
 
817
745
  if (
@@ -854,8 +782,8 @@ class BandsWidget(VBox):
854
782
 
855
783
  @property
856
784
  def results(self):
857
- "Generate a data frame form result.json file in each folder."
858
- return load_results(self.files_widget.paths)
785
+ "Generate a dataframe form result.json file in each folder."
786
+ return load_results(self._interact._dd.options)
859
787
 
860
788
 
861
789
  @fix_signature
@@ -870,9 +798,11 @@ class KpathWidget(VBox):
870
798
  - To update point(s), select point(s) from the select box and click on a scatter point in figure or use KPOINT input to update it manually, e.g. if a point is not available on plot.
871
799
  - Add labels to the points by typing in the "Labels" box such as "Γ,X" or "Γ 5,X" that will add 5 points in interval.
872
800
  - To break the path between two points "Γ" and "X" type "Γ 0,X" in the "Labels" box, zero means no points in interval.
801
+
802
+ You can use `self.files.update` method to change source files without effecting state of widget.
873
803
  """
874
804
 
875
- def __init__(self, height="400px", **files_widget_kwargs):
805
+ def __init__(self, files, height="400px"):
876
806
  super().__init__(_dom_classes=["KpathWidget"])
877
807
  self._fig = go.FigureWidget()
878
808
  self._sm = SelectMultiple(options=[], layout=Layout(width="auto"))
@@ -894,13 +824,11 @@ class KpathWidget(VBox):
894
824
  self._lab,
895
825
  self._kpt,
896
826
  ]
897
- files_widget_kwargs = {"glob": "POSCAR", **files_widget_kwargs}
898
- self._interact = FilesWidget(**files_widget_kwargs).interactive(
827
+
828
+ Files(files)._attributed_interactive(self,
899
829
  self._update_fig, [self._fig], other_controls, height=height
900
830
  )
901
- self.files_widget = self._interact.files_widget # sometimes useful
902
- self.children = self._interact.children
903
-
831
+
904
832
  self._tsb.on_click(self._update_theme)
905
833
  self._add.on_click(self._toggle_lock)
906
834
  self._del.on_click(self._del_point)
@@ -910,7 +838,12 @@ class KpathWidget(VBox):
910
838
  @property
911
839
  def path(self):
912
840
  "Returns currently selected path."
913
- return self._interact.files_widget.path
841
+ return self._interact._dd.value # itself a Path object
842
+
843
+ @property
844
+ def files(self):
845
+ "Use slef.files.update(...) to keep state of widget preserved."
846
+ return self._files
914
847
 
915
848
  @property
916
849
  def poscar(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipyvasp
3
- Version: 0.9.2
3
+ Version: 0.9.3
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,25 +1,25 @@
1
- ipyvasp/__init__.py,sha256=7o41i5eYlNKg1Hsv0DLNFZ81GilxB02IXAJN-QiJQi0,1420
1
+ ipyvasp/__init__.py,sha256=rlorju9arMtHw1QRYPljday-PyZWJdSCxg4lw3g6t0Q,1409
2
2
  ipyvasp/__main__.py,sha256=eJV1TZSiT8mC_VqAeksNnBI2I8mKMiPkEIlwikbtOjI,216
3
3
  ipyvasp/_enplots.py,sha256=D38paN8zqZgluNAwmCwcocd7-_h_T0HTGolI1eBkDes,37484
4
- ipyvasp/_lattice.py,sha256=DN3VZVjQ5fbzUrbpft75ieaP5ddyr6mIuZRHOwrFrK8,103052
5
- ipyvasp/_version.py,sha256=I3ASgj0LYAmGGQhqxkAYEEiiLOpQ1UAZGOd0bJAXSRw,23
6
- ipyvasp/bsdos.py,sha256=ZtQji-W11UdFFicAoWZjlqVhI5tqYu_jpKyPPWKkeeo,30634
4
+ ipyvasp/_lattice.py,sha256=GxG0C4lwVGvBYIy3jwR1kahWR7L6kJlqjIiQGgESjcM,104135
5
+ ipyvasp/_version.py,sha256=5uBZ3sUaocnyIWuJeW9M94Z77vA1kKmwKxiblrnbKlc,23
6
+ ipyvasp/bsdos.py,sha256=1rG68S-dLEYveIWGK7r8CRa7Qqlqno0l1ncfo2ocihk,30424
7
7
  ipyvasp/cli.py,sha256=aWFEVhNmnW8eSOp5uh95JaDwLQ9K9nlCQcbnOSuhWgw,6844
8
8
  ipyvasp/evals_dataframe.py,sha256=-sqxK7LPV6sYDO_XXmZ80FznOaXTkVdbqJKKvTUtMak,20637
9
- ipyvasp/lattice.py,sha256=wz5CKv5YJbELvG2lbrYnXoT7u9R70Jkr_i3olE4Hx4I,30643
9
+ ipyvasp/lattice.py,sha256=t4s1qh6IJsoeXcCa9M9IhjAQp2s78lqiGhOfEkCW19s,30638
10
10
  ipyvasp/misc.py,sha256=SZJ_ePUR2-HEKYTEpDHVRVE7zpIQVTCjiuw0BCC9UTU,2349
11
11
  ipyvasp/potential.py,sha256=tzA73c5lkp6ahLSJchMrU043-QWaOV0nIOUA7VMmfKQ,11408
12
12
  ipyvasp/surface.py,sha256=MjE5oB0wW6Pca_C-xu8rN6OMH7lUEeNPNyM7Kz_Im-8,23766
13
- ipyvasp/utils.py,sha256=hiAV76jEMuDt1Wp22imrnsSgetjxmUKlskRA7CcK6BU,15261
14
- ipyvasp/widgets.py,sha256=fZ2b7EYYxuaAVfklnLa0VJ00U9Uyd7SqXrzt0hbLOvI,45400
13
+ ipyvasp/utils.py,sha256=rVyD5SkO_Y7ok5W-fJeQys9X8pLLbDK7VOgbAbcE4WU,14227
14
+ ipyvasp/widgets.py,sha256=mP1tqYO-tPk4Xq_fgkGPGN2AfRf9V-al3Yl7vzKp5lU,43758
15
15
  ipyvasp/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  ipyvasp/core/parser.py,sha256=C3CaZsJbPME_ttYlYy4DXeOdL7dnkXs-cHRwFZL6bio,38058
17
17
  ipyvasp/core/plot_toolkit.py,sha256=8t5svyWbOm-PS7ZvIptnK6F46kp6uwoGNdohPv5dQKA,35962
18
18
  ipyvasp/core/serializer.py,sha256=XpqnfVGsUXiN2CuVRPyqsSVouxRBO-UH6AnsHnPYvZY,36729
19
19
  ipyvasp/core/spatial_toolkit.py,sha256=8DBYTiBFWJ7OBKuvOPw7UoEVCyNjJhSW0OcudjYZvAw,14748
20
- ipyvasp-0.9.2.dist-info/LICENSE,sha256=F3SO5RiAZOMfmMGf1KOuk2g_c4ObvuBJhd9iBLDgXoQ,1263
21
- ipyvasp-0.9.2.dist-info/METADATA,sha256=rbDpOJNtuinZ9AJi0pBcBTAYHLuoC_IbBvQagjXSHG4,2420
22
- ipyvasp-0.9.2.dist-info/WHEEL,sha256=iYlv5fX357PQyRT2o6tw1bN-YcKFFHKqB_LwHO5wP-g,110
23
- ipyvasp-0.9.2.dist-info/entry_points.txt,sha256=C7m0Sjmr14wFjflCkWXLzr5N6-cQj8uJC9n82mUtzt8,44
24
- ipyvasp-0.9.2.dist-info/top_level.txt,sha256=ftziWlMWu_1VpDP1sRTFrkfBnWxAi393HYDVu4wRhUk,8
25
- ipyvasp-0.9.2.dist-info/RECORD,,
20
+ ipyvasp-0.9.3.dist-info/LICENSE,sha256=F3SO5RiAZOMfmMGf1KOuk2g_c4ObvuBJhd9iBLDgXoQ,1263
21
+ ipyvasp-0.9.3.dist-info/METADATA,sha256=v2jvMQ--zKh-b0GAF5XywZDMKJi8x3hUAMMvKGdfiD0,2420
22
+ ipyvasp-0.9.3.dist-info/WHEEL,sha256=iYlv5fX357PQyRT2o6tw1bN-YcKFFHKqB_LwHO5wP-g,110
23
+ ipyvasp-0.9.3.dist-info/entry_points.txt,sha256=C7m0Sjmr14wFjflCkWXLzr5N6-cQj8uJC9n82mUtzt8,44
24
+ ipyvasp-0.9.3.dist-info/top_level.txt,sha256=ftziWlMWu_1VpDP1sRTFrkfBnWxAi393HYDVu4wRhUk,8
25
+ ipyvasp-0.9.3.dist-info/RECORD,,