ipyvasp 0.9.2__py2.py3-none-any.whl → 0.9.4__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.4"
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
@@ -1019,7 +1019,7 @@ def iplot2widget(fig, fig_widget=None, template=None):
1019
1019
  elif not isinstance(fig_widget, go.FigureWidget):
1020
1020
  raise ValueError("fig_widget must be FigureWidget")
1021
1021
  else:
1022
- scene = fig_widget.layout.scene # keep scene from widget
1022
+ scene = fig_widget.layout.scene if fig_widget.data else fig.layout.scene# keep scene from widget, but if looks a new fig, keep from previous
1023
1023
 
1024
1024
  fig_widget.data = [] # Clear previous data
1025
1025
  if template is not None:
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,257 +99,215 @@ 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
182
-
183
- @out.capture(clear_output=True, wait=True)
184
- def on_change(change):
185
- on_file_changed(self.selected)
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.
186
113
 
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
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 map(self,func):
163
+ "Map files to a function!"
164
+ return map(func, self._files)
165
+
166
+ def with_name(self, name):
167
+ "Change name of all files. Only keeps existing files."
168
+ return self.__class__([f.with_name(name) for f in self._files])
169
+
170
+ def filtered(self, include=None, exclude=None, files_only = False, dirs_only=False):
171
+ "Filter all files. Only keeps existing file."
172
+ files = [p for p in self._files if re.search(include, str(p))] if include else self._files
173
+ return self.__class__(files, exclude=exclude,dirs_only=dirs_only,files_only=files_only)
243
174
 
244
- @property
245
- def path(self): # This is in consistent with other widgets too
246
- "Return currently selected path."
247
- return self.selected
248
-
249
- def interactive(
250
- self,
251
- func,
252
- other_widgets=None,
253
- other_controls=None,
175
+ def summarize(self, func, **kwargs):
176
+ "Apply a func(apth) -> dict and create a dataframe."
177
+ return summarize(self._files,func, **kwargs)
178
+
179
+ def load_results(self):
180
+ "Load result.json files from these paths into a dataframe."
181
+ return load_results(self._files)
182
+
183
+ def input_info(self, *tags):
184
+ "Grab input information into a dataframe from POSCAR and INCAR. Provide INCAR tags (case-insinsitive) to select only few of them."
185
+ from .lattice import POSCAR
186
+
187
+ def info(path, tags):
188
+ p = POSCAR(path).data
189
+ lines = [[v.strip() for v in line.split('=')]
190
+ for line in path.with_name('INCAR').read_text().splitlines()
191
+ if '=' in line]
192
+ if tags:
193
+ tags = [t.upper() for t in tags] # can send lowercase tag
194
+ lines = [(k,v) for k,v in lines if k in tags]
195
+ d = {k:v for k,v in lines if not k.startswith('#')}
196
+ d.update({k:len(v) for k,v in p.types.items()})
197
+ d.update(zip('abcvαβγ', [*p.norms,p.volume,*p.angles]))
198
+ return d
199
+
200
+ return self.with_name('POSCAR').summarize(info, tags=tags)
201
+
202
+ def update(self, path_or_files, glob = '*',**kwargs):
203
+ "Update files inplace with similar parameters as initialization. Useful for widgets such as BandsWidget to preserve their state while files swapping."
204
+ self._files = self.__class__(path_or_files, glob = glob, **kwargs)
205
+ if (dd := getattr(self, '_dd', None)): # update dropdown
206
+ old = dd.value
207
+ dd.options = self._files
208
+ if old in dd.options:
209
+ dd.value = old
210
+
211
+ def interactive(self, func, *args,
212
+ free_widgets=None,
254
213
  options={"manual": False},
255
214
  height="400px",
256
- **kwargs,
257
- ):
215
+ panel_size = 25,
216
+ **kwargs):
258
217
  """
259
- 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.
218
+ Interact with `func(path, *args, <free_widgets,optional>, **kwargs)` that takes selected Path as first argument. Returns a widget that saves attributes of the function call such as .f, .args, .kwargs.
219
+ `args` are widgets to be modified by func, such as plotly's FigureWidget.
220
+ Note that `free_widgets` can also be passed to function but does not run function when changed.
221
+
260
222
  See docs of self.interact for more details on the parameters. kwargs are passed to ipywidgets.interactive to create controls.
261
223
 
224
+ >>> import plotly.graph_objects as go
225
+ >>> import ipyvasp as ipv
226
+ >>> fs = ipv.Files('.','**/POSCAR')
227
+ >>> def plot(path, fig, bl,plot_cell,eqv_sites):
228
+ >>> ipv.iplot2widget(ipv.POSCAR(path).iplot_lattice(
229
+ >>> bond_length=bl,plot_cell=plot_cell,eqv_sites=eqv_sites
230
+ >>> ),fig_widget=fig) # it keeps updating same view
231
+ >>> out = fs.interactive(plot, go.FigureWidget(),bl=(0,6,2),plot_cell=True, eqv_sites=True)
262
232
 
263
- >>> fw = FilesWidget()
264
- >>> out = fw.interactive(lambda path: print(path.read_text())) # prints contents of selected file on output widget
265
233
  >>> out.f # function
266
234
  >>> out.args # arguments
267
235
  >>> out.kwargs # keyword arguments
268
236
  >>> 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
237
 
272
238
  .. note::
273
239
  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
240
  """
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")
241
+ info = ipw.HTML().add_class("fprogess")
242
+ dd = Dropdown(description='File', options=self._files)
285
243
 
286
- def interact_func(fname, **kwargs):
244
+ def interact_func(fname, **kws):
287
245
  if fname: # This would be None if no file is selected
288
246
  info.value = _progress_svg
289
247
  try:
290
248
  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
249
+ if 'free_widgets' in func.__code__.co_varnames:
250
+ kws['free_widgets'] = free_widgets # user allowed to pass this
251
+ names = ', '.join(func.__code__.co_varnames)
252
+ print(f"Running {func.__name__}({names})")
253
+ func(Path(fname).absolute(), *args, **kws) # Have Path object absolue if user changes directory
297
254
  print(f"Finished in {time() - start:.3f} seconds.")
298
255
  finally:
299
256
  info.value = ""
300
257
 
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
258
+ out = ipw.interactive(interact_func, options, fname=dd, **kwargs)
259
+ out._dd = dd # save reference to dropdown
260
+ out.output_widget = out.children[-1] # save reference to output widget for other widgets to use
309
261
 
310
262
  if options.get("manual", False):
311
- out.interact_button = out.children[
312
- -2
313
- ] # save reference to interact button for other widgets to use
263
+ out.interact_button = out.children[-2] # save reference to interact button for other widgets to use
314
264
 
315
265
  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
266
+ output.clear_output(wait=True) # clear output by waiting to avoid flickering, this is important
319
267
  output.layout = Layout(
320
268
  overflow="auto", max_height="100%", width="100%"
321
269
  ) # make output scrollable and avoid overflow
322
270
 
323
271
  others = out.children[1:-1] # exclude files_dd and Output widget
324
- _style = """<style>
325
- .FW-Interact {
272
+ if not isinstance(panel_size,int):
273
+ raise TypeError('panel_size should be integer in units of em')
274
+
275
+ _style = f"""<style>
276
+ .files-interact {{
326
277
  --jp-widgets-inline-label-width: 4em;
327
- --jp-widgets-inline-width: 18em;
278
+ --jp-widgets-inline-width: {panel_size-2}em;
328
279
  --jp-widgets-inline-width-short: 9em;
329
- }
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}
280
+ }}
281
+ .files-interact {{max-height:{height};width:100%;}}
282
+ .files-interact > div {{overflow:auto;max-height:100%;padding:8px;}}
283
+ .files-interact > div:first-child {{width:{panel_size}em}}
284
+ .files-interact > div:last-child {{width:calc(100% - {panel_size}em)}}
285
+ .files-interact .fprogess {{position:absolute !important; left:50%; top:50%; transform:translate(-50%,-50%); z-index:1}}
335
286
  </style>"""
336
287
  if others:
337
288
  others = [ipw.HTML(f"<hr/>{_style}"), *others]
338
289
  else:
339
290
  others = [ipw.HTML(_style)]
340
291
 
341
- if other_controls and not isinstance(other_controls, (list, tuple)):
342
- raise TypeError("other_controls must be a list or tuple of widgets.")
343
-
344
- if other_widgets and not isinstance(other_widgets, (list, tuple)):
345
- raise TypeError("other_widgets must be a list or tuple of widgets.")
346
-
347
- if other_widgets:
292
+ if free_widgets and not isinstance(free_widgets, (list, tuple)):
293
+ raise TypeError("free_widgets must be a list or tuple of widgets.")
294
+
295
+ for w in args:
296
+ if not isinstance(w,ipw.DOMWidget):
297
+ raise TypeError(f'args can only contain a DOMWidget instance, got {type(w)}')
298
+
299
+ if args:
348
300
  output.layout.max_height = "200px"
301
+ output.layout.min_height = "8em" # fisrt fix
349
302
  out_collapser = Checkbox(description="Hide output widget", value=False)
350
303
 
351
304
  def toggle_output(change):
352
305
  if out_collapser.value:
353
306
  output.layout.height = "0px" # dont use display = 'none' as it will clear widgets and wont show again
307
+ output.layout.min_height = "0px"
354
308
  else:
355
309
  output.layout.height = "auto"
310
+ output.layout.min_height = "8em"
356
311
 
357
312
  out_collapser.observe(toggle_output, "value")
358
313
  others.append(out_collapser)
@@ -361,94 +316,92 @@ class FilesWidget(VBox):
361
316
  others = [
362
317
  *others,
363
318
  ipw.HTML(f"<hr/>"),
364
- *(other_controls or []),
319
+ *(free_widgets or []),
365
320
  ] # add hr to separate other controls
366
321
 
367
322
  out.children = [
368
323
  HBox(
369
324
  [ # reset children to include new widgets
370
325
  VBox(
371
- children=[new_fw, VBox(others)]
326
+ children=[dd, VBox(others)]
372
327
  ), # other widgets in box to make scrollable independent file selection
373
328
  VBox(
374
- children=[Box([output]), *(other_widgets or []), info]
329
+ children=[output, *args, info]
375
330
  ), # output in box to make scrollable,
376
331
  ],
377
332
  layout=Layout(height=height, max_height=height),
378
- ).add_class("FW-Interact")
333
+ ).add_class("files-interact")
379
334
  ] # important for every widget separately
380
335
  return out
381
-
382
- def interact(
383
- self,
384
- other_widgets=None,
385
- other_controls=None,
336
+
337
+ def _attributed_interactive(self, box, func, *args, **kwargs):
338
+ box._files = self
339
+ box._interact = self.interactive(func, *args, **kwargs)
340
+ box.children = box._interact.children
341
+ box._files._dd = box._interact._dd
342
+
343
+ def interact(self, *args,
344
+ free_widgets=None,
386
345
  options={"manual": False},
387
346
  height="400px",
347
+ panel_size=25,
388
348
  **kwargs,
389
349
  ):
390
- """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.
350
+ """Interact with a `func(path, *args, <free_widgets,optional>, **kwargs)`. `path` is passed from selected File.
351
+ A CSS class 'files-interact' is added to the final widget to let you style it.
392
352
 
393
353
  Parameters
394
354
  ----------
395
- other_widgets : list/tuple
355
+ args :
396
356
  Any displayable widget can be passed. These are placed below the output widget of interact.
397
- For example you can add plotly's FigureWidget that updates based on the selection, but is not part of the function, so it is displayed only once.
398
- other_controls : list/tuple
399
- Default is None. If not None, these are assumed to be ipywidgets and are placed below the widgets created by kwargs. These are not passed to the decorated function.
357
+ For example you can add plotly's FigureWidget that updates based on the selection, these are passed to function after path.
358
+ free_widgets : list/tuple
359
+ Default is None. If not None, these are assumed to be ipywidgets and are placed below the widgets created by kwargs.
360
+ These can be passed to the decorated function if added as arguemnt there like `func(..., free_widgets)`, but don't trigger execution.
400
361
  options : dict
401
- Default is {'manua':False}. If True, the decorated function is not called automatically, and you have to call it manually on button press. You can pass button name as 'manual_name' in options.
362
+ Default is {'manual':False}. If True, the decorated function is not called automatically, and you have to call it manually on button press. You can pass button name as 'manual_name' in options.
402
363
  height : str
403
364
  Default is '90vh'. height of the final widget. This is important to avoid very long widgets.
404
-
365
+ panel_size: int
366
+ Side panel size in units of em.
405
367
 
406
368
  kwargs are passed to ipywidgets.interactive and decorated function. Resulting widgets are placed below the file selection widget.
407
-
408
- `other_widgets` can be controlled by `other_controls` externally. For example, you can add a button to update a plotly's FigureWidget.
369
+ Widgets in `args` can be controlled by `free_widgets` externally if defined gloablly or inside function if you pass `free_widgets` as argument like `func(..., free_widgets)`.
409
370
 
410
371
  The decorated function can be called later separately as well, and has .args and .kwargs attributes to access the latest arguments
411
372
  and .result method to access latest. For a function `f`, `f.result` is same as `f(*f.args, **f.kwargs)`.
412
373
 
413
-
414
- >>> fw = FilesWidget()
415
- >>> @fw.interact(x = False)
416
- >>> def f(path,x):
417
- >>> print('path:',path)
418
- >>> print('Path Type: ', type(path))
419
- >>> print('x: ',x)
420
-
374
+ >>> import plotly.graph_objects as go
375
+ >>> import ipyvasp as ipv
376
+ >>> fs = ipv.Files('.','**/POSCAR')
377
+ >>> @fs.interact(go.FigureWidget(),bl=(0,6,2),plot_cell=True, eqv_sites=True)
378
+ >>> def plot(path, fig, bl,plot_cell,eqv_sites):
379
+ >>> ipv.iplot2widget(ipv.POSCAR(path).iplot_lattice(
380
+ >>> bond_length=bl,plot_cell=plot_cell,eqv_sites=eqv_sites
381
+ >>> ),fig_widget=fig) # it keeps updating same view
421
382
 
422
383
  .. note::
423
384
  Use self.interactive to get a widget that stores the argements and can be called later in a notebook cell.
424
385
  """
425
386
 
426
387
  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
- )
388
+ display(self.interactive(func, *args,
389
+ free_widgets=free_widgets,
390
+ options=options,
391
+ height=height,
392
+ panel_size=panel_size,
393
+ **kwargs)
436
394
  )
437
395
  return func
438
-
439
396
  return inner
397
+
398
+ def kpath_widget(self, height='400px'):
399
+ "Get KpathWidget instance with these files."
400
+ return KpathWidget(files = self.with_name('POSCAR'), height = height)
440
401
 
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
- )
402
+ def bands_widget(self, height='450px'):
403
+ "Get BandsWidget instance with these files."
404
+ return BandsWidget(files=self._files, height=height)
452
405
 
453
406
 
454
407
  @fix_signature
@@ -647,12 +600,12 @@ class BandsWidget(VBox):
647
600
  Two attributes are important:
648
601
  self.clicked_data returns the last clicked point, that can also be stored as VBM, CBM etc, using Click dropdown.
649
602
  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.
603
+ You can use `self.files.update` method to change source files without effecting state of widget.
650
604
  """
651
605
 
652
- def __init__(self, use_vaspout=False, height="450px", **file_widget_kwargs):
606
+ def __init__(self, files, height="450px"):
653
607
  super().__init__(_dom_classes=["BandsWidget"])
654
608
  self._bands = None
655
- self._use_vaspout = use_vaspout
656
609
  self._fig = go.FigureWidget()
657
610
  self._tsd = Dropdown(
658
611
  description="Style", options=["plotly_white", "plotly_dark"]
@@ -668,14 +621,9 @@ class BandsWidget(VBox):
668
621
  self._click_dict = {} # store clicked data
669
622
  self._select_dict = {} # store selection data
670
623
  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(
676
- self._load_data,
677
- other_widgets=[self._fig],
678
- other_controls=[
624
+
625
+ Files(files)._attributed_interactive(self, self._load_data, self._fig,
626
+ free_widgets=[
679
627
  self._tsd,
680
628
  self._brange,
681
629
  self._ktcicks,
@@ -686,8 +634,7 @@ class BandsWidget(VBox):
686
634
  ],
687
635
  height=height,
688
636
  )
689
- self.files_widget = self._interact.files_widget
690
- self.children = self._interact.children
637
+
691
638
  self._tsd.observe(self._change_theme, "value")
692
639
  self._click.observe(self._click_save_data, "value")
693
640
  self._ktcicks.observe(self._warn_update, "value")
@@ -696,13 +643,19 @@ class BandsWidget(VBox):
696
643
  @property
697
644
  def path(self):
698
645
  "Returns currently selected path."
699
- return self.files_widget.path
646
+ return self._interact._dd.value
647
+
648
+ @property
649
+ def files(self):
650
+ "Use slef.files.update(...) to keep state of widget preserved."
651
+ return self._files
700
652
 
701
- def _load_data(self, path): # Automatically redirectes to output widget
653
+ def _load_data(self, path, fig): # Automatically redirectes to output widget
654
+ if not hasattr(self, '_interact'): return # First time not availablebu
702
655
  self._interact.output_widget.clear_output(wait=True) # Why need again?
703
656
  with self._interact.output_widget:
704
657
  self._bands = (
705
- vp.Vaspout(path) if self._use_vaspout else vp.Vasprun(path)
658
+ vp.Vasprun(path) if path.parts[-1].endswith('xml') else vp.Vaspout(path)
706
659
  ).bands
707
660
  self._ppicks.update(self.bands.source.summary)
708
661
  self._ktcicks.value = ", ".join(
@@ -714,7 +667,7 @@ class BandsWidget(VBox):
714
667
  else:
715
668
  self._click.options = ["None", "VBM", "CBM"]
716
669
 
717
- if (file := self.files_widget.selected.parent / "result.json").is_file():
670
+ if (file := path.parent / "result.json").is_file():
718
671
  self._result = serializer.load(str(file.absolute())) # Old data loaded
719
672
 
720
673
  pdata = self.bands.source.poscar.data
@@ -756,6 +709,7 @@ class BandsWidget(VBox):
756
709
  return self._select_dict.get("data", None)
757
710
 
758
711
  def _update_graph(self, btn):
712
+ if not hasattr(self, '_interact'): return # First time not available
759
713
  self._interact.output_widget.clear_output(wait=True) # Why need again?
760
714
  with self._interact.output_widget:
761
715
  hsk = [
@@ -811,7 +765,7 @@ class BandsWidget(VBox):
811
765
  serializer.dump(
812
766
  data_dict,
813
767
  format="json",
814
- outfile=self.files_widget.selected.parent / "result.json",
768
+ outfile=self.path.parent / "result.json",
815
769
  )
816
770
 
817
771
  if (
@@ -854,8 +808,8 @@ class BandsWidget(VBox):
854
808
 
855
809
  @property
856
810
  def results(self):
857
- "Generate a data frame form result.json file in each folder."
858
- return load_results(self.files_widget.paths)
811
+ "Generate a dataframe form result.json file in each folder."
812
+ return load_results(self._interact._dd.options)
859
813
 
860
814
 
861
815
  @fix_signature
@@ -870,9 +824,11 @@ class KpathWidget(VBox):
870
824
  - 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
825
  - 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
826
  - To break the path between two points "Γ" and "X" type "Γ 0,X" in the "Labels" box, zero means no points in interval.
827
+
828
+ You can use `self.files.update` method to change source files without effecting state of widget.
873
829
  """
874
830
 
875
- def __init__(self, height="400px", **files_widget_kwargs):
831
+ def __init__(self, files, height="400px"):
876
832
  super().__init__(_dom_classes=["KpathWidget"])
877
833
  self._fig = go.FigureWidget()
878
834
  self._sm = SelectMultiple(options=[], layout=Layout(width="auto"))
@@ -885,7 +841,7 @@ class KpathWidget(VBox):
885
841
  self._clicktime = None
886
842
  self._kpoints = {}
887
843
 
888
- other_controls = [
844
+ free_widgets = [
889
845
  HBox([self._add, self._del, self._tsb], layout=Layout(min_height="24px")),
890
846
  ipw.HTML(
891
847
  "<style>.KpathWidget .widget-select-multiple { min-height: 180px; }\n .widget-select-multiple > select {height: 100%;}</style>"
@@ -894,13 +850,11 @@ class KpathWidget(VBox):
894
850
  self._lab,
895
851
  self._kpt,
896
852
  ]
897
- files_widget_kwargs = {"glob": "POSCAR", **files_widget_kwargs}
898
- self._interact = FilesWidget(**files_widget_kwargs).interactive(
899
- self._update_fig, [self._fig], other_controls, height=height
853
+
854
+ Files(files)._attributed_interactive(self,
855
+ self._update_fig, self._fig, free_widgets=free_widgets, height=height
900
856
  )
901
- self.files_widget = self._interact.files_widget # sometimes useful
902
- self.children = self._interact.children
903
-
857
+
904
858
  self._tsb.on_click(self._update_theme)
905
859
  self._add.on_click(self._toggle_lock)
906
860
  self._del.on_click(self._del_point)
@@ -910,14 +864,20 @@ class KpathWidget(VBox):
910
864
  @property
911
865
  def path(self):
912
866
  "Returns currently selected path."
913
- return self._interact.files_widget.path
867
+ return self._interact._dd.value # itself a Path object
868
+
869
+ @property
870
+ def files(self):
871
+ "Use slef.files.update(...) to keep state of widget preserved."
872
+ return self._files
914
873
 
915
874
  @property
916
875
  def poscar(self):
917
876
  "POSCAR class associated to current selection."
918
877
  return self._poscar
919
878
 
920
- def _update_fig(self, path):
879
+ def _update_fig(self, path, fig):
880
+ if not hasattr(self, '_interact'): return # First time not available
921
881
  from .lattice import POSCAR # to avoid circular import
922
882
 
923
883
  with self._interact.output_widget:
@@ -926,10 +886,10 @@ class KpathWidget(VBox):
926
886
  )
927
887
  self._poscar = POSCAR(path)
928
888
  ptk.iplot2widget(
929
- self._poscar.iplot_bz(fill=False, color="red"), self._fig, template
889
+ self._poscar.iplot_bz(fill=False, color="red"), fig, template
930
890
  )
931
- with self._fig.batch_animate():
932
- self._fig.add_trace(
891
+ with fig.batch_animate():
892
+ fig.add_trace(
933
893
  go.Scatter3d(
934
894
  x=[],
935
895
  y=[],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipyvasp
3
- Version: 0.9.2
3
+ Version: 0.9.4
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,25 @@
1
+ ipyvasp/__init__.py,sha256=rlorju9arMtHw1QRYPljday-PyZWJdSCxg4lw3g6t0Q,1409
2
+ ipyvasp/__main__.py,sha256=eJV1TZSiT8mC_VqAeksNnBI2I8mKMiPkEIlwikbtOjI,216
3
+ ipyvasp/_enplots.py,sha256=D38paN8zqZgluNAwmCwcocd7-_h_T0HTGolI1eBkDes,37484
4
+ ipyvasp/_lattice.py,sha256=GxG0C4lwVGvBYIy3jwR1kahWR7L6kJlqjIiQGgESjcM,104135
5
+ ipyvasp/_version.py,sha256=iPcoATf7BiWjSu-KocRdM5zFTR4wx4ktCHlGGpvdc1M,23
6
+ ipyvasp/bsdos.py,sha256=1rG68S-dLEYveIWGK7r8CRa7Qqlqno0l1ncfo2ocihk,30424
7
+ ipyvasp/cli.py,sha256=aWFEVhNmnW8eSOp5uh95JaDwLQ9K9nlCQcbnOSuhWgw,6844
8
+ ipyvasp/evals_dataframe.py,sha256=-sqxK7LPV6sYDO_XXmZ80FznOaXTkVdbqJKKvTUtMak,20637
9
+ ipyvasp/lattice.py,sha256=t4s1qh6IJsoeXcCa9M9IhjAQp2s78lqiGhOfEkCW19s,30638
10
+ ipyvasp/misc.py,sha256=SZJ_ePUR2-HEKYTEpDHVRVE7zpIQVTCjiuw0BCC9UTU,2349
11
+ ipyvasp/potential.py,sha256=tzA73c5lkp6ahLSJchMrU043-QWaOV0nIOUA7VMmfKQ,11408
12
+ ipyvasp/surface.py,sha256=MjE5oB0wW6Pca_C-xu8rN6OMH7lUEeNPNyM7Kz_Im-8,23766
13
+ ipyvasp/utils.py,sha256=rVyD5SkO_Y7ok5W-fJeQys9X8pLLbDK7VOgbAbcE4WU,14227
14
+ ipyvasp/widgets.py,sha256=iWkH7PbzZFPUZ2LrSRLafTPxXfiRfNFG5Ddaq3Wd_BU,45655
15
+ ipyvasp/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
+ ipyvasp/core/parser.py,sha256=C3CaZsJbPME_ttYlYy4DXeOdL7dnkXs-cHRwFZL6bio,38058
17
+ ipyvasp/core/plot_toolkit.py,sha256=3RoPsND5gPssBSfS5H4TjoZ2Qz7B97vpxeKadc2cRRs,36046
18
+ ipyvasp/core/serializer.py,sha256=XpqnfVGsUXiN2CuVRPyqsSVouxRBO-UH6AnsHnPYvZY,36729
19
+ ipyvasp/core/spatial_toolkit.py,sha256=8DBYTiBFWJ7OBKuvOPw7UoEVCyNjJhSW0OcudjYZvAw,14748
20
+ ipyvasp-0.9.4.dist-info/LICENSE,sha256=F3SO5RiAZOMfmMGf1KOuk2g_c4ObvuBJhd9iBLDgXoQ,1263
21
+ ipyvasp-0.9.4.dist-info/METADATA,sha256=PvaEna3-zdOpEQhg5_iD9ECRt7N4nqI5aPluv6-VJ9M,2420
22
+ ipyvasp-0.9.4.dist-info/WHEEL,sha256=iYlv5fX357PQyRT2o6tw1bN-YcKFFHKqB_LwHO5wP-g,110
23
+ ipyvasp-0.9.4.dist-info/entry_points.txt,sha256=C7m0Sjmr14wFjflCkWXLzr5N6-cQj8uJC9n82mUtzt8,44
24
+ ipyvasp-0.9.4.dist-info/top_level.txt,sha256=ftziWlMWu_1VpDP1sRTFrkfBnWxAi393HYDVu4wRhUk,8
25
+ ipyvasp-0.9.4.dist-info/RECORD,,
@@ -1,25 +0,0 @@
1
- ipyvasp/__init__.py,sha256=7o41i5eYlNKg1Hsv0DLNFZ81GilxB02IXAJN-QiJQi0,1420
2
- ipyvasp/__main__.py,sha256=eJV1TZSiT8mC_VqAeksNnBI2I8mKMiPkEIlwikbtOjI,216
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
7
- ipyvasp/cli.py,sha256=aWFEVhNmnW8eSOp5uh95JaDwLQ9K9nlCQcbnOSuhWgw,6844
8
- ipyvasp/evals_dataframe.py,sha256=-sqxK7LPV6sYDO_XXmZ80FznOaXTkVdbqJKKvTUtMak,20637
9
- ipyvasp/lattice.py,sha256=wz5CKv5YJbELvG2lbrYnXoT7u9R70Jkr_i3olE4Hx4I,30643
10
- ipyvasp/misc.py,sha256=SZJ_ePUR2-HEKYTEpDHVRVE7zpIQVTCjiuw0BCC9UTU,2349
11
- ipyvasp/potential.py,sha256=tzA73c5lkp6ahLSJchMrU043-QWaOV0nIOUA7VMmfKQ,11408
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
15
- ipyvasp/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
- ipyvasp/core/parser.py,sha256=C3CaZsJbPME_ttYlYy4DXeOdL7dnkXs-cHRwFZL6bio,38058
17
- ipyvasp/core/plot_toolkit.py,sha256=8t5svyWbOm-PS7ZvIptnK6F46kp6uwoGNdohPv5dQKA,35962
18
- ipyvasp/core/serializer.py,sha256=XpqnfVGsUXiN2CuVRPyqsSVouxRBO-UH6AnsHnPYvZY,36729
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,,