ipyvasp 0.8.0__tar.gz → 0.8.1__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.8.0 → ipyvasp-0.8.1}/PKG-INFO +6 -3
  2. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/README.md +5 -2
  3. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/_lattice.py +122 -65
  4. ipyvasp-0.8.1/ipyvasp/_version.py +1 -0
  5. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/bsdos.py +22 -17
  6. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/core/serializer.py +16 -4
  7. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/lattice.py +105 -0
  8. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/widgets.py +10 -9
  9. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp.egg-info/PKG-INFO +6 -3
  10. ipyvasp-0.8.0/ipyvasp/_version.py +0 -1
  11. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/LICENSE +0 -0
  12. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/__init__.py +0 -0
  13. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/__main__.py +0 -0
  14. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/_enplots.py +0 -0
  15. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/cli.py +0 -0
  16. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/core/__init__.py +0 -0
  17. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/core/parser.py +0 -0
  18. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/core/plot_toolkit.py +0 -0
  19. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/core/spatial_toolkit.py +0 -0
  20. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/evals_dataframe.py +0 -0
  21. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/misc.py +0 -0
  22. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/potential.py +0 -0
  23. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp/utils.py +0 -0
  24. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp.egg-info/SOURCES.txt +0 -0
  25. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp.egg-info/dependency_links.txt +0 -0
  26. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp.egg-info/entry_points.txt +0 -0
  27. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp.egg-info/requires.txt +0 -0
  28. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/ipyvasp.egg-info/top_level.txt +0 -0
  29. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/setup.cfg +0 -0
  30. {ipyvasp-0.8.0 → ipyvasp-0.8.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipyvasp
3
- Version: 0.8.0
3
+ Version: 0.8.1
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
@@ -32,7 +32,7 @@ Requires-Dist: nglview>=3.0.4; extra == "extra"
32
32
 
33
33
  # ipyvasp
34
34
 
35
- An successor of [pivotpy](https://github.com/massgh/pivotpy) for VASP-based DFT pre and post processing tool.
35
+ An VASP-based DFT pre and post processing tool.
36
36
 
37
37
  ## Install
38
38
  Currently the package is being built and not stable. If you want to use development version, install this way:(recommended to install in a virtual environment)
@@ -68,6 +68,9 @@ Interactively select bandstructure path by clicking on high symmetry points on p
68
68
 
69
69
  ![KP](KP.png)
70
70
 
71
- More coming soon!
71
+ Apply operations on POSCAR and simultaneously view using plotly's `FigureWidget` or `WeasWidget` in Jupyterlab side by side.
72
+
73
+ ![snip](op.png)
72
74
 
73
75
 
76
+ More coming soon!
@@ -1,6 +1,6 @@
1
1
  # ipyvasp
2
2
 
3
- An successor of [pivotpy](https://github.com/massgh/pivotpy) for VASP-based DFT pre and post processing tool.
3
+ An VASP-based DFT pre and post processing tool.
4
4
 
5
5
  ## Install
6
6
  Currently the package is being built and not stable. If you want to use development version, install this way:(recommended to install in a virtual environment)
@@ -36,6 +36,9 @@ Interactively select bandstructure path by clicking on high symmetry points on p
36
36
 
37
37
  ![KP](KP.png)
38
38
 
39
- More coming soon!
39
+ Apply operations on POSCAR and simultaneously view using plotly's `FigureWidget` or `WeasWidget` in Jupyterlab side by side.
40
40
 
41
+ ![snip](op.png)
41
42
 
43
+
44
+ More coming soon!
@@ -4,7 +4,7 @@ import numpy as np
4
4
  from pathlib import Path
5
5
  import requests as req
6
6
  import inspect
7
- from itertools import combinations
7
+ from itertools import combinations, product
8
8
  from functools import lru_cache
9
9
 
10
10
  from scipy.spatial import ConvexHull, KDTree
@@ -215,7 +215,7 @@ def periodic_table():
215
215
  return ax
216
216
 
217
217
 
218
- def write_poscar(poscar_data, outfile=None, selective_dynamics=None, overwrite=False):
218
+ def write_poscar(poscar_data, outfile=None, selective_dynamics=None, overwrite=False, comment=""):
219
219
  """Writes POSCAR data to a file or returns string
220
220
 
221
221
  Parameters
@@ -227,13 +227,15 @@ def write_poscar(poscar_data, outfile=None, selective_dynamics=None, overwrite=F
227
227
  See `ipyvasp.POSCAR.data.get_selective_dynamics` for more info.
228
228
  overwrite: bool
229
229
  If file already exists, overwrite=True changes it.
230
+ comment: str
231
+ Add comment, previous comment will be there too.
230
232
 
231
233
 
232
234
  .. note::
233
235
  POSCAR is only written in direct format even if it was loaded from cartesian format.
234
236
  """
235
- _comment = poscar_data.metadata.comment
236
- out_str = f"{poscar_data.SYSTEM} # " + (_comment or "Created by Pivopty")
237
+ _comment = poscar_data.metadata.comment + comment
238
+ out_str = f"{poscar_data.SYSTEM} # " + (_comment or "Created by ipyvasp")
237
239
  scale = poscar_data.metadata.scale
238
240
  out_str += "\n {:<20.14f}\n".format(scale)
239
241
  out_str += "\n".join(
@@ -531,9 +533,9 @@ class InvokeMaterialsProject:
531
533
  else:
532
534
  print(self._cif)
533
535
 
534
- def write_poscar(self, outfile=None, overwrite=False):
536
+ def write_poscar(self, outfile=None, overwrite=False, comment=""):
535
537
  "Use `ipyvasp.lattice.POSCAR.write` if you need extra options."
536
- write_poscar(self.export_poscar(), outfile=outfile, overwrite=overwrite)
538
+ write_poscar(self.export_poscar(), outfile=outfile, overwrite=overwrite, comment=comment)
537
539
 
538
540
  def export_poscar(self):
539
541
  "Export poscar data form cif content."
@@ -1578,18 +1580,18 @@ def _get_bond_length(poscar_data, bond_length=None):
1578
1580
  ) # Add 5% margin over mean distance, this covers same species too, and in multiple species, this will stop bonding between same species.
1579
1581
 
1580
1582
 
1581
- def _masked_data(poscar_data, mask_sites):
1582
- "Returns indices of sites which satisfy the mask_sites function."
1583
- if not callable(mask_sites):
1584
- raise TypeError("`mask_sites` should be a callable function.")
1583
+ def _masked_data(poscar_data, func):
1584
+ "Returns indices of sites which satisfy the func."
1585
+ if not callable(func):
1586
+ raise TypeError("`func` should be a callable function.")
1585
1587
 
1586
- if len(inspect.signature(mask_sites).parameters) != 4:
1588
+ if len(inspect.signature(func).parameters) != 4:
1587
1589
  raise ValueError(
1588
- "`mask_sites` takes exactly 4 arguments: (index,x,y,z) in fractional coordinates"
1590
+ "`func` takes exactly 4 arguments: (index,x,y,z) in fractional coordinates"
1589
1591
  )
1590
1592
 
1591
- if not isinstance(mask_sites(0, 0, 0, 0), bool):
1592
- raise TypeError("`mask_sites` should return a boolean value.")
1593
+ if not isinstance(func(0, 0, 0, 0), bool):
1594
+ raise TypeError("`func` should return a boolean value.")
1593
1595
 
1594
1596
  eqv_inds = None
1595
1597
  if hasattr(poscar_data.metadata, "eqv_indices"):
@@ -1598,7 +1600,7 @@ def _masked_data(poscar_data, mask_sites):
1598
1600
  pick = []
1599
1601
  for i, pos in enumerate(poscar_data.positions):
1600
1602
  idx = eqv_inds[i] if eqv_inds else i # map to original index
1601
- if mask_sites(idx, *pos):
1603
+ if func(idx, *pos):
1602
1604
  pick.append(i)
1603
1605
  return pick # could be duplicate indices
1604
1606
 
@@ -1620,6 +1622,43 @@ def _filter_pairs(labels, pairs, dist, bond_length):
1620
1622
  return pairs # None -> auto calculate bond_length, number -> use that number
1621
1623
 
1622
1624
 
1625
+ def filter_sites(poscar_data, func, tol = 0.01):
1626
+ """Filter sites based on a function that acts on index and fractional positions such as `lambda i,x,y,z: condition`.
1627
+ This may include equivalent sites, so it should be used for plotting purpose only, e.g. showing atoms on a plane."""
1628
+ idxs = _masked_data(poscar_data, func)
1629
+ data = poscar_data.to_dict()
1630
+ all_pos, npos = [], []
1631
+ for value in poscar_data.types.values():
1632
+ indices = [i for i in value if i in idxs]
1633
+ others = [j for j in value if not (j in indices)]
1634
+ pos = data['positions'][indices]
1635
+ qos = data['positions'][others] # need edge items to include
1636
+
1637
+ if qos.shape:
1638
+ qos = np.concatenate([[[i] for i in others], qos], axis=1) # need to keep index
1639
+ qos = np.array([qos + [0, *p] for p in product([-1,0,1],[-1,0,1],[-1,0,1])]).reshape((-1,4)) # all possible translations
1640
+ qos = qos[(qos[:,1:] < 1 + tol).all(axis=1) & (qos[:,1:] > -tol).all(axis=1)]# only in cell range
1641
+ qos = qos[[func(*q) for q in qos]] # masked only those are true
1642
+
1643
+ if qos.shape:
1644
+ pos = np.concatenate([pos, qos[:,1:]],axis=0)
1645
+
1646
+ all_pos.append(pos)
1647
+ npos.append(len(pos))
1648
+
1649
+ if not np.sum(npos):
1650
+ raise ValueError("No sites found with given filter func!")
1651
+
1652
+ if not 'prev_positions' in data['metadata']: # keep always the starting one
1653
+ data['metadata']['prev_positions'] = poscar_data.positions
1654
+ data['metadata']['prev_types'] = poscar_data.types
1655
+
1656
+ data['positions'] = np.concatenate(all_pos, axis = 0)
1657
+
1658
+ ranges = np.cumsum([0, *npos])
1659
+ data['types'] = {key: range(i,j) for key, i,j in zip(data['types'],ranges[:-1],ranges[1:]) if range(i,j)} # avoid empty
1660
+ return serializer.PoscarData(data)
1661
+
1623
1662
  # Cell
1624
1663
  def iplot_lattice(
1625
1664
  poscar_data,
@@ -1632,7 +1671,6 @@ def iplot_lattice(
1632
1671
  origin=(0, 0, 0),
1633
1672
  fig=None,
1634
1673
  ortho3d=True,
1635
- mask_sites=None,
1636
1674
  bond_kws=dict(line_width=4),
1637
1675
  site_kws=dict(line_color="rgba(1,1,1,0)", line_width=0.001, opacity=1),
1638
1676
  plot_cell=True,
@@ -1648,9 +1686,6 @@ def iplot_lattice(
1648
1686
  Sequence of colors for each type. Automatically generated if not provided.
1649
1687
  bond_length : float or dict
1650
1688
  Length of bond in Angstrom. Auto calculated if not provides. Can be a dict like {'Fe-O':3.2,...} to specify bond length between specific types.
1651
- mask_sites : callable
1652
- Provide a mask function `f(index, x,y,z) -> bool` to show only selected sites.
1653
- For example, to show only sites with z > 0.5, use `mask_sites = lambda i, x,y,z: x > 0.5`.
1654
1689
  bond_kws : dict
1655
1690
  Keyword arguments passed to `plotly.graph_objects.Scatter3d` for bonds.
1656
1691
  Default is jus hint, you can use any keyword argument that is accepted by `plotly.graph_objects.Scatter3d`.
@@ -1664,23 +1699,17 @@ def iplot_lattice(
1664
1699
 
1665
1700
  kwargs are passed to `iplot_bz`.
1666
1701
  """
1702
+ if len(poscar_data.positions) < 1:
1703
+ raise ValueError("Need at least 1 atom!")
1704
+
1667
1705
  poscar_data = _fix_sites(
1668
1706
  poscar_data, tol=tol, eqv_sites=eqv_sites, translate=translate, origin=origin
1669
1707
  )
1708
+
1670
1709
  blen = _get_bond_length(poscar_data, bond_length)
1671
1710
 
1672
- sites = None
1673
- coords = poscar_data.coords
1674
- if (
1675
- mask_sites is not None
1676
- ): # not None is important, as it can be False given by user
1677
- sites = _masked_data(poscar_data, mask_sites)
1678
- coords = poscar_data.coords[sites]
1679
- if not sites:
1680
- raise ValueError("No sites found with given mask_sites function.")
1681
-
1682
- coords, pairs, dist = get_pairs(coords, r=blen)
1683
- _labels = poscar_data.labels[sites] if sites else poscar_data.labels
1711
+ coords, pairs, dist = get_pairs(poscar_data.coords, r=blen)
1712
+ _labels = poscar_data.labels
1684
1713
  pairs = _filter_pairs(_labels, pairs, dist, bond_length)
1685
1714
 
1686
1715
  if not fig:
@@ -1738,13 +1767,8 @@ def iplot_lattice(
1738
1767
  **site_kws,
1739
1768
  }
1740
1769
  for (k, v), c, s in zip(uelems.items(), colors, sizes):
1741
- if sites:
1742
- v = [i for i in v if i in sites] # Only show selected sites.
1743
- coords = poscar_data.coords[v]
1744
- labs = poscar_data.labels[v]
1745
- else:
1746
- coords = poscar_data.coords[v]
1747
- labs = poscar_data.labels[v]
1770
+ coords = poscar_data.coords[v]
1771
+ labs = poscar_data.labels[v]
1748
1772
 
1749
1773
  fig.add_trace(
1750
1774
  go.Scatter3d(
@@ -1830,7 +1854,6 @@ def splot_lattice(
1830
1854
  translate=None,
1831
1855
  origin=(0, 0, 0),
1832
1856
  ax=None,
1833
- mask_sites=None,
1834
1857
  showlegend=True,
1835
1858
  fmt_label=None,
1836
1859
  site_kws=dict(alpha=0.7),
@@ -1852,9 +1875,6 @@ def splot_lattice(
1852
1875
  Length of bond in Angstrom. Auto calculated if not provides. Can be a dict like {'Fe-O':3.2,...} to specify bond length between specific types.
1853
1876
  alpha : float
1854
1877
  Opacity of points and bonds.
1855
- mask_sites : callable
1856
- Provide a mask function `f(index, x,y,z) -> bool` to show only selected sites.
1857
- For example, to show only sites with z > 0.5, use `mask_sites = lambda i,x,y,z: x > 0.5`.
1858
1878
  showlegend : bool
1859
1879
  Default is True, show legend for each ion type.
1860
1880
  site_kws : dict
@@ -1875,6 +1895,9 @@ def splot_lattice(
1875
1895
  .. tip::
1876
1896
  Use `plt.style.use('ggplot')` for better 3D perception.
1877
1897
  """
1898
+ if len(poscar_data.positions) < 1:
1899
+ raise ValueError("Need at least 1 atom!")
1900
+
1878
1901
  # Plane fix
1879
1902
  if plane and plane not in "xyzxzyx":
1880
1903
  raise ValueError("plane expects in 'xyzxzyx' or None.")
@@ -1887,21 +1910,8 @@ def splot_lattice(
1887
1910
  poscar_data, tol=tol, eqv_sites=eqv_sites, translate=translate, origin=origin
1888
1911
  )
1889
1912
  blen = _get_bond_length(poscar_data, bond_length)
1890
-
1891
- sites = None
1892
- coords = poscar_data.coords # take all sites
1893
- filtered_elems = list(poscar_data.types.keys())
1894
- if mask_sites is not None: # not None is important, user can give anything
1895
- sites = _masked_data(poscar_data, mask_sites)
1896
- if not sites:
1897
- raise ValueError("No sites found with given mask_sites function.")
1898
-
1899
- coords = poscar_data.coords[sites]
1900
- filtered_elems = np.unique(poscar_data.symbols[sites]).tolist()
1901
-
1902
- labels = [poscar_data.labels[i] for i in sites] if sites else poscar_data.labels
1903
-
1904
- coords, pairs, dist = get_pairs(coords, r=blen)
1913
+ labels = poscar_data.labels
1914
+ coords, pairs, dist = get_pairs(poscar_data.coords, r=blen)
1905
1915
  pairs = _filter_pairs(labels, pairs, dist, bond_length)
1906
1916
 
1907
1917
  if fmt_label is not None:
@@ -1939,10 +1949,7 @@ def splot_lattice(
1939
1949
  # Before doing other stuff, create something for legend.
1940
1950
  if showlegend:
1941
1951
  for key, c, s in zip(uelems.keys(), colors, sizes):
1942
- if key in filtered_elems:
1943
- ax.scatter(
1944
- [], [], s=s, color=c, label=key, **site_kws
1945
- ) # Works both for 3D and 2D.
1952
+ ax.scatter([], [], s=s, color=c, label=key, **site_kws) # Works both for 3D and 2D.
1946
1953
  ptk.add_legend(ax)
1947
1954
 
1948
1955
  # Now change colors and sizes to whole array size
@@ -1951,10 +1958,6 @@ def splot_lattice(
1951
1958
  )
1952
1959
  sizes = np.array([sizes[i] for i, vs in enumerate(uelems.values()) for v in vs])
1953
1960
 
1954
- if sites:
1955
- colors = colors[sites]
1956
- sizes = sizes[sites]
1957
-
1958
1961
  if np.any(pairs):
1959
1962
  coords_p = coords[pairs] # paired points
1960
1963
  _colors = colors[pairs] # Colors at pairs
@@ -2218,6 +2221,40 @@ def scale_poscar(poscar_data, scale=(1, 1, 1), tol=1e-2):
2218
2221
  new_poscar["positions"] = np.array(positions)
2219
2222
  return serializer.PoscarData(new_poscar)
2220
2223
 
2224
+ def set_boundary(poscar_data, a = [0,1], b = [0,1], c = [0,1]):
2225
+ "View atoms outside cell boundary along a,b,c directions."
2226
+ for d, name in zip([a,b,c],'abc'):
2227
+ if not isinstance(d,(list,tuple)) or len(d) != 2:
2228
+ raise ValueError(f"{name} should be a list/tuple of type [min, max]")
2229
+ if d[1] < d[0]:
2230
+ raise ValueError(f"{name} should be in increasing order as [min, max]")
2231
+
2232
+ data = poscar_data.to_dict()
2233
+ upos = {}
2234
+ for key, value in poscar_data.types.items():
2235
+ pos = data['positions'][value]
2236
+ for i, (l,h), shift in zip(range(3), [a,b,c],np.eye(3)):
2237
+ while pos[:,i].min() > np.floor(l):
2238
+ pos = np.concatenate([pos, pos - shift],axis=0)
2239
+
2240
+ while pos[:,i].max() < np.ceil(h):
2241
+ pos = np.concatenate([pos, pos + shift],axis=0)
2242
+
2243
+ pos = pos[(pos[:,i] >= l) & (pos[:,i] <= h)]
2244
+
2245
+ upos[key] = pos
2246
+
2247
+ data['positions'] = np.concatenate(list(upos.values()), axis = 0)
2248
+
2249
+ ranges = np.cumsum([0, *[len(v) for v in upos.values()]])
2250
+ data['types'] = {key: range(i,j) for key, i,j in zip(upos,ranges[:-1],ranges[1:])}
2251
+ del upos
2252
+ return serializer.PoscarData(data)
2253
+
2254
+
2255
+
2256
+
2257
+
2221
2258
 
2222
2259
  def rotate_poscar(poscar_data, angle_deg, axis_vec):
2223
2260
  """Rotate a given POSCAR.
@@ -2560,6 +2597,26 @@ def replace_atoms(poscar_data, func, name):
2560
2597
  }
2561
2598
  return serializer.PoscarData(data) # Return new POSCAR
2562
2599
 
2600
+ def sort_poscar(poscar_data, new_order):
2601
+ "sort poscar with new_order list/tuple of species."
2602
+ if not isinstance(new_order, (list, tuple)):
2603
+ raise TypeError(f"new_order should be a list/tuple of types, got {type(new_order)}")
2604
+
2605
+ data = poscar_data.to_dict()
2606
+ if not all([set(new_order).issubset(data["types"]), set(data["types"]).issubset(new_order)]):
2607
+ raise ValueError(f"new_order should contain all existings types {list(data['types'])}")
2608
+
2609
+ data["types"] = {key:data["types"][key] for key in new_order}
2610
+ data["positions"] = data["positions"][[i for tp in data["types"].values() for i in tp]]
2611
+ idxs = np.cumsum([0, *map(len, data["types"].values())])
2612
+ data["types"] = {
2613
+ k: range(idxs[i], idxs[i + 1])
2614
+ for i, k in enumerate(data["types"].keys())
2615
+ if len(data["types"][k]) != 0
2616
+ }
2617
+
2618
+ return serializer.PoscarData(data)
2619
+
2563
2620
 
2564
2621
  def remove_atoms(poscar_data, func, fillby=None):
2565
2622
  """Remove atoms that satisfy `func(x,y,z) -> bool` on their fractional coordinates x,y,z.
@@ -0,0 +1 @@
1
+ __version__ = "0.8.1"
@@ -149,7 +149,9 @@ _spin_doc = """spin : int
149
149
  plotting other with same parameters will use the same data."""
150
150
  _kind_doc = """kpairs : list/tuple
151
151
  List of pair of indices to rearrange a computed path. For example, if you computed
152
- 0:L, 15:G, 25:X, 34:M path and want to plot it as X-G|M-X, use [(25,15), (34,25)] as kpairs."""
152
+ 0:L, 15:G, 25:X, 34:M path and want to plot it as X-G|M-X, use [(25,15), (34,25)] as kpairs.
153
+ bands : list/tuple
154
+ List of indices of bands. If given, this ovverides elim."""
153
155
  _proj_doc = """projections : dict
154
156
  Mapping from str -> [atoms, orbs]. Use dict to select specific projections,
155
157
  e.g. {'Ga-s': (0,[0]), 'Ga1-p': ([0],[1,2,3])} in case of GaAs. If values of the dict
@@ -335,7 +337,7 @@ class Bands(_BandsDosBase):
335
337
 
336
338
  return serializer.Dict2Data(out)
337
339
 
338
- def get_data(self, elim=None, ezero=None, projections: dict = None, kpairs=None):
340
+ def get_data(self, elim=None, ezero=None, projections: dict = None, kpairs=None, bands=None):
339
341
  """
340
342
  Selects bands and projections to use in plotting functions. If input arguments are same as previous call, returns cached data.
341
343
 
@@ -345,12 +347,13 @@ class Bands(_BandsDosBase):
345
347
  ezero : float, None by default. If not None, elim is applied around this energy.
346
348
  projections : dict, str -> [atoms, orbs]. Use dict to select specific projections, e.g. {'Ga-s': (0,[0]), 'Ga1-p': ([0],[1,2,3])} in case of GaAs. If values of the dict are callable, they must accept two arguments evals, occs of shape (spin,kpoints, bands) and return array of shape (kpoints, bands).
347
349
  kpairs : list, tuple of integers, None by default to select all kpoints in given order. Use this to select specific kpoints intervals in specific order.
350
+ bands : list,tuple of integers, this ovverides elim if given.
348
351
 
349
352
  Returns
350
353
  -------
351
354
  data : Selected bands and projections data to be used in bandstructure plotting functions under this class as `data` argument.
352
355
  """
353
- if self.data and self._data_args == (elim, ezero, projections, kpairs):
356
+ if self.data and self._data_args == (elim, ezero, projections, kpairs, bands):
354
357
  return self.data
355
358
 
356
359
  if kpairs and not isinstance(kpairs, (list, tuple)):
@@ -377,7 +380,7 @@ class Bands(_BandsDosBase):
377
380
  kpairs[-1][-1],
378
381
  ] # flatten and add last index
379
382
 
380
- self._data_args = (elim, ezero, projections)
383
+ self._data_args = (elim, ezero, projections, bands)
381
384
 
382
385
  (
383
386
  (spins, uspins),
@@ -393,7 +396,7 @@ class Bands(_BandsDosBase):
393
396
  atoms=uatoms,
394
397
  orbs=uorbs,
395
398
  spins=uspins or None,
396
- bands=None,
399
+ bands=bands,
397
400
  ) # picks available spins if uspins is None
398
401
 
399
402
  if not spins:
@@ -508,9 +511,9 @@ class Bands(_BandsDosBase):
508
511
  {"K :.*ax :": f"{_spin_doc}\n{_kind_doc}\nax :"},
509
512
  )
510
513
  @_sig_kwargs(splot_bands, ("K", "E"))
511
- def splot_bands(self, spin=0, kpairs=None, ezero=None, **kwargs):
514
+ def splot_bands(self, spin=0, kpairs=None, ezero=None, bands=None, **kwargs):
512
515
  kwargs, elim = self._handle_kwargs(spin=spin, **kwargs)
513
- data = self.get_data(elim=elim, ezero=ezero, kpairs=kpairs)
516
+ data = self.get_data(elim=elim, ezero=ezero, kpairs=kpairs, bands=bands)
514
517
  return splot_bands(data.kpath, data.evals[spin] - data.ezero, **kwargs)
515
518
 
516
519
  @_sub_doc(
@@ -518,9 +521,9 @@ class Bands(_BandsDosBase):
518
521
  {"K :.*ax :": f"{_proj_doc}\n{_spin_doc}\n{_kind_doc}\nax :"},
519
522
  )
520
523
  @_sig_kwargs(splot_rgb_lines, ("K", "E", "pros", "labels"))
521
- def splot_rgb_lines(self, projections, spin=0, kpairs=None, ezero=None, **kwargs):
524
+ def splot_rgb_lines(self, projections, spin=0, kpairs=None, ezero=None, bands=None, **kwargs):
522
525
  kwargs, elim = self._handle_kwargs(spin=spin, **kwargs)
523
- data = self.get_data(elim, ezero, projections, kpairs=kpairs)
526
+ data = self.get_data(elim, ezero, projections, kpairs=kpairs, bands=bands)
524
527
  return splot_rgb_lines(
525
528
  data.kpath, data.evals[spin] - data.ezero, data.pros, data.labels, **kwargs
526
529
  )
@@ -530,10 +533,10 @@ class Bands(_BandsDosBase):
530
533
  {"K :.*axes :": f"{_proj_doc}\n{_spin_doc}\n{_kind_doc}\naxes :"},
531
534
  )
532
535
  @_sig_kwargs(splot_color_lines, ("K", "E", "pros", "labels"))
533
- def splot_color_lines(self, projections, spin=0, kpairs=None, ezero=None, **kwargs):
536
+ def splot_color_lines(self, projections, spin=0, kpairs=None, ezero=None, bands=None, **kwargs):
534
537
  kwargs, elim = self._handle_kwargs(spin=spin, **kwargs)
535
538
  data = self.get_data(
536
- elim, ezero, projections, kpairs=kpairs
539
+ elim, ezero, projections, kpairs=kpairs, bands=bands
537
540
  ) # picked relative limit
538
541
  return splot_color_lines(
539
542
  data.kpath, data.evals[spin] - data.ezero, data.pros, data.labels, **kwargs
@@ -544,9 +547,9 @@ class Bands(_BandsDosBase):
544
547
  {"K :.*fig :": f"{_proj_doc}\n{_spin_doc}\n{_kind_doc}\nfig :"},
545
548
  )
546
549
  @_sig_kwargs(iplot_rgb_lines, ("K", "E", "pros", "labels", "occs", "kpoints"))
547
- def iplot_rgb_lines(self, projections, spin=0, kpairs=None, ezero=None, **kwargs):
550
+ def iplot_rgb_lines(self, projections, spin=0, kpairs=None, ezero=None, bands=None, **kwargs):
548
551
  kwargs, elim = self._handle_kwargs(spin=spin, **kwargs)
549
- data = self.get_data(elim, ezero, projections, kpairs=kpairs)
552
+ data = self.get_data(elim, ezero, projections, kpairs=kpairs, bands=bands)
550
553
  # Send K and bands in place of K for use in iplot_rgb_lines to depict correct band number
551
554
  return iplot_rgb_lines(
552
555
  {"K": data.kpath, "indices": data.bands},
@@ -563,9 +566,9 @@ class Bands(_BandsDosBase):
563
566
  {"K :.*fig :": f"{_spin_doc}\n{_kind_doc}\nfig :"},
564
567
  )
565
568
  @_sig_kwargs(iplot_bands, ("K", "E"))
566
- def iplot_bands(self, spin=0, kpairs=None, ezero=None, **kwargs):
569
+ def iplot_bands(self, spin=0, kpairs=None, ezero=None, bands=None, **kwargs):
567
570
  kwargs, elim = self._handle_kwargs(spin=spin, **kwargs)
568
- data = self.get_data(elim, ezero, kpairs=kpairs)
571
+ data = self.get_data(elim, ezero, kpairs=kpairs, bands=bands)
569
572
  # Send K and bands in place of K for use in iplot_rgb_lines to depict correct band number
570
573
  return iplot_bands(
571
574
  {"K": data.kpath, "indices": data.bands},
@@ -573,13 +576,14 @@ class Bands(_BandsDosBase):
573
576
  **kwargs,
574
577
  )
575
578
 
576
- def view_bands(self):
579
+ def view_bands(self, height="450px"):
577
580
  "Initialize and return `ipyvasp.widgets.BandsWidget` to view bandstructure interactively."
578
581
  use_vaspout = True if isinstance(self.source, vp.Vaspout) else False
579
582
  return wdg.BandsWidget(
580
583
  use_vaspout=use_vaspout,
581
- path=self.source.path.parent,
584
+ path=str(self.source.path.parent),
582
585
  glob=self.source.path.name,
586
+ height=height,
583
587
  )
584
588
 
585
589
 
@@ -677,6 +681,7 @@ class DOS(_BandsDosBase):
677
681
  raise ValueError("spin must be 0,1,2,3 for dos")
678
682
 
679
683
  kwargs.pop("spin", None) # remove from kwargs as plots don't need it
684
+ kwargs.pop("bands", None) # not required internally
680
685
  return kwargs, kwargs.get("elim", None)
681
686
 
682
687
  @_sub_doc(
@@ -211,11 +211,20 @@ class Dict2Data:
211
211
  def values(self):
212
212
  return self.__dict__.values()
213
213
 
214
- def __getitem__(self, key):
215
- return self.__dict__[key]
216
-
217
214
  def items(self):
218
215
  return self.__dict__.items()
216
+
217
+ def __getitem__(self, key):
218
+ return self.__dict__[key]
219
+
220
+ def __contains__(self, key):
221
+ return key in self.__dict__
222
+
223
+ def __iter__(self):
224
+ return iter(self.__dict__)
225
+
226
+ def __len__(self):
227
+ return len(self.__dict__)
219
228
 
220
229
 
221
230
  class SpinData(Dict2Data):
@@ -375,6 +384,9 @@ class PoscarData(Dict2Data):
375
384
  raise ValueError(
376
385
  "atom2 must be a string such as 'As' or a dict with a single key as {'As':0}"
377
386
  )
387
+
388
+ if len(set([*idx1, *idx2])) < 2: # itself or no atom exists
389
+ return np.nan # No mean of distnace in this case
378
390
 
379
391
  dists = []
380
392
  for idx in idx1:
@@ -388,7 +400,7 @@ class PoscarData(Dict2Data):
388
400
 
389
401
  dists = np.array(dists)
390
402
  dists = dists[dists > 0] # Remove distance with itself
391
- return np.min(dists)
403
+ return np.min(dists) if dists.size else np.nan
392
404
 
393
405
  def to_fractional(self, coords):
394
406
  "Converts cartesian coordinates to fractional coordinates in the basis of cell."
@@ -12,6 +12,8 @@ __all__ = [
12
12
  from pathlib import Path
13
13
  from contextlib import redirect_stdout
14
14
  from io import StringIO
15
+ from itertools import permutations
16
+ from contextlib import suppress
15
17
 
16
18
  import numpy as np
17
19
  from pandas.io.clipboard import clipboard_get, clipboard_set
@@ -198,6 +200,78 @@ def ngl_viewer(
198
200
  return view
199
201
 
200
202
 
203
+ def weas_viewer(poscar,
204
+ sizes=None,
205
+ colors=None,
206
+ bond_length=None,
207
+ model_style = 1,
208
+ plot_cell=True
209
+ ):
210
+ """
211
+ colors : list or str
212
+ List of colors for each atom type. If str, use 'VESTA','JMOL','CPK'.
213
+ By default, ipyvasp colors are used.
214
+ sizes : list
215
+ List of sizes for each atom type.
216
+ model_type: int
217
+ whether to show Balls (0), Ball + Stick (1), Polyheda (2) or Sticks (3).
218
+ plot_cell : bool
219
+ Plot unit cell. Default True.
220
+ bond_length : float or dict
221
+ Length of bond in Angstrom. Auto calculated if not provides. Can be a dict like {'Fe-O':3.2,...} to specify bond length between specific types.
222
+
223
+ Returns a WeasWidget instance. You can use `.export_image`, `save_image` and other operations on it.
224
+ """
225
+
226
+ from weas_widget import WeasWidget
227
+
228
+ if len(poscar.data.positions) < 1:
229
+ raise ValueError("Need at least 1 atom!")
230
+
231
+ w = WeasWidget(from_ase=poscar.to_ase())
232
+ w.avr.show_bonded_atoms = True
233
+ w.avr.model_style = model_style
234
+ w.avr.show_cell = plot_cell
235
+
236
+
237
+ if bond_length:
238
+ if isinstance(bond_length,(int,float)):
239
+ for a,b in permutations(list(poscar.data.types.keys()),2):
240
+ with suppress(KeyError):
241
+ w.avr.bond.settings[f'{a}-{b}'].update({'max': bond_length})
242
+ elif isinstance(bond_length, dict):
243
+ for key, value in bond_length.items():
244
+ w.avr.bond.settings[key].update({'max': value})
245
+
246
+ if sizes is not None:
247
+ if not isinstance(sizes, (list,tuple)) or len(sizes) != len(poscar.data.types):
248
+ raise ValueError(f"sizes should be list/tuple of same sizes as atom types = {len(poscar.data.types)}.")
249
+ for key, value in zip(poscar.data.types,sizes):
250
+ w.avr.species.settings[key].update({"radius": value})
251
+
252
+ if colors is None:
253
+ colors = [plat._atom_colors[key] for key in poscar.data.types]
254
+
255
+ if isinstance(colors, str):
256
+ if not colors in ['VESTA','JMOL','CPK']:
257
+ raise ValueError("colors should be one of ['VESTA','JMOL','CPK'] if given as string!")
258
+
259
+ w.avr.color_type = colors
260
+
261
+ if not isinstance(colors, str):
262
+ if not isinstance(colors, (list,tuple)) or len(colors) != len(poscar.data.types):
263
+ raise ValueError(f"colors should be list/tuple of same sizes as atom types = {len(poscar.data.types)}.")
264
+
265
+ colors = {key: value for key, value in zip(poscar.data.types,colors)}
266
+ for key,value in colors.items():
267
+ w.avr.species.settings[key].update({"color": value})
268
+ for (k1,c1), (k2,c2) in permutations(colors.items(),2):
269
+ with suppress(KeyError):
270
+ w.avr.bond.settings[f'{k1}-{k2}'].update({'color1':c1,'color2':c2})
271
+
272
+ return w
273
+
274
+
201
275
  class POSCAR:
202
276
  _cb_instance = {} # Loads last clipboard data if not changed
203
277
  _mp_instance = {} # Loads last mp data if not changed
@@ -272,6 +346,8 @@ class POSCAR:
272
346
  """
273
347
  if viewer is None:
274
348
  return plat.view_poscar(self.data, **kwargs)
349
+ elif viewer in "weas":
350
+ return weas_viewer(self, **kwargs)
275
351
  elif viewer in "nglview":
276
352
  return print(
277
353
  f"Use `self.view_ngl()` for better customization in case of viewer={viewer!r}"
@@ -285,6 +361,12 @@ class POSCAR:
285
361
  @_sig_kwargs(ngl_viewer, ("poscar",))
286
362
  def view_ngl(self, **kwargs):
287
363
  return ngl_viewer(self, **kwargs)
364
+
365
+ @_sub_doc(weas_viewer)
366
+ @_sig_kwargs(weas_viewer, ("poscar",))
367
+ def view_weas(self, **kwargs):
368
+ return weas_viewer(self, **kwargs)
369
+
288
370
 
289
371
  def view_kpath(self):
290
372
  "Initialize a KpathWidget instance to view kpath for current POSCAR, and you can select others too."
@@ -446,6 +528,14 @@ class POSCAR:
446
528
  "Writes POSCAR to clipboard (as implemented by pandas library) for copy in other programs such as vim."
447
529
  clipboard_set(self.content) # write to clipboard
448
530
 
531
+
532
+ def update_weas(self, handle):
533
+ """Send result of any options to view on opened weas widget handle with default parameters becuase any operations
534
+ can be incompatible with presious state of POSCAR. VESTA color scheme is used here.
535
+ Useful in Jupyterlab side panel to look operations results on POSCAR."""
536
+ handle.from_ase(self.to_ase())
537
+ handle.avr.color_type = 'VESTA'
538
+
449
539
  @property
450
540
  def data(self):
451
541
  "Data object in POSCAR."
@@ -454,6 +544,11 @@ class POSCAR:
454
544
  def copy(self):
455
545
  "Copy POSCAR object. It avoids accidental changes to numpy arrays in original object."
456
546
  return self.__class__(data=self.data.copy())
547
+
548
+ @_sub_doc(plat.sort_poscar)
549
+ def sort(self, new_order):
550
+ return self.__class__(data=plat.sort_poscar(self.data, new_order))
551
+
457
552
 
458
553
  @property
459
554
  def content(self):
@@ -546,6 +641,16 @@ class POSCAR:
546
641
  @_sig_kwargs(plat.scale_poscar, ("poscar_data",))
547
642
  def scale(self, scale=(1, 1, 1), **kwargs):
548
643
  return self.__class__(data=plat.scale_poscar(self.data, scale, **kwargs))
644
+
645
+ @_sub_doc(plat.set_boundary)
646
+ @_sig_kwargs(plat.set_boundary,("poscar_data",))
647
+ def set_boundary(self, a = [0,1], b=[0,1],c=[0,1]):
648
+ return self.__class__(data = plat.set_boundary(self.data, a=a,b=b,c=c))
649
+
650
+ @_sub_doc(plat.filter_sites)
651
+ @_sig_kwargs(plat.filter_sites,("poscar_data",))
652
+ def filter_sites(self, func):
653
+ return self.__class__(data = plat.filter_sites(self.data, func))
549
654
 
550
655
  @_sub_doc(plat.set_origin)
551
656
  def set_origin(self, origin):
@@ -252,7 +252,7 @@ class FilesWidget(VBox):
252
252
  other_widgets=None,
253
253
  other_controls=None,
254
254
  options={"manual": False},
255
- height="90vh",
255
+ height="400px",
256
256
  **kwargs,
257
257
  ):
258
258
  """
@@ -384,7 +384,7 @@ class FilesWidget(VBox):
384
384
  other_widgets=None,
385
385
  other_controls=None,
386
386
  options={"manual": False},
387
- height="90vh",
387
+ height="400px",
388
388
  **kwargs,
389
389
  ):
390
390
  """Interact with a function that takes a selected Path as first argument.
@@ -649,7 +649,7 @@ class BandsWidget(VBox):
649
649
  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.
650
650
  """
651
651
 
652
- def __init__(self, use_vaspout=False, height="90vh", **file_widget_kwargs):
652
+ def __init__(self, use_vaspout=False, height="450px", **file_widget_kwargs):
653
653
  super().__init__(_dom_classes=["BandsWidget"])
654
654
  self._bands = None
655
655
  self._use_vaspout = use_vaspout
@@ -659,7 +659,7 @@ class BandsWidget(VBox):
659
659
  )
660
660
  self._click = Dropdown(description="Click", options=["None", "VBM", "CBM"])
661
661
  self._ktcicks = Text(description="kticks")
662
- self._elim = Text(description="elim", value="-10, 10")
662
+ self._brange = ipw.IntRangeSlider(description="bands",min=1, max=1) # number, not index
663
663
  self._ppicks = PropsPicker(
664
664
  on_button_click=self._update_graph, on_selection_changed=self._warn_update
665
665
  )
@@ -677,7 +677,7 @@ class BandsWidget(VBox):
677
677
  other_widgets=[self._fig],
678
678
  other_controls=[
679
679
  self._tsd,
680
- self._elim,
680
+ self._brange,
681
681
  self._ktcicks,
682
682
  ipw.HTML("<hr/>"),
683
683
  self._ppicks,
@@ -691,7 +691,7 @@ class BandsWidget(VBox):
691
691
  self._tsd.observe(self._change_theme, "value")
692
692
  self._click.observe(self._click_save_data, "value")
693
693
  self._ktcicks.observe(self._warn_update, "value")
694
- self._elim.observe(self._warn_update, "value")
694
+ self._brange.observe(self._warn_update, "value")
695
695
 
696
696
  @property
697
697
  def path(self):
@@ -708,6 +708,7 @@ class BandsWidget(VBox):
708
708
  self._ktcicks.value = ", ".join(
709
709
  f"{k}:{v}" for k, v in self.bands.get_kticks()
710
710
  )
711
+ self._brange.max = self.bands.source.summary.NBANDS
711
712
  if self.bands.source.summary.LSORBIT:
712
713
  self._click.options = ["None", "VBM", "CBM", "so_max", "so_min"]
713
714
  else:
@@ -762,8 +763,8 @@ class BandsWidget(VBox):
762
763
  for vs in self._ktcicks.value.split(",")
763
764
  ]
764
765
  kticks = [(int(vs[0]), vs[1]) for vs in hsk if len(vs) == 2] or None
765
- elim = [float(v) for v in self._elim.value.split(",") if v.strip()] or None
766
- self._kwargs = {"elim": elim, "kticks": kticks}
766
+ self._kwargs = {"kticks": kticks, # below numbers instead of index and full shown range
767
+ "bands": range(self._brange.value[0] - 1, self._brange.value[1]) if self._brange.value else None}
767
768
 
768
769
  if self._ppicks.projections:
769
770
  self._kwargs = {"projections": self._ppicks.projections, **self._kwargs}
@@ -871,7 +872,7 @@ class KpathWidget(VBox):
871
872
  - To break the path between two points "Γ" and "X" type "Γ 0,X" in the "Labels" box, zero means no points in interval.
872
873
  """
873
874
 
874
- def __init__(self, height="90vh", **files_widget_kwargs):
875
+ def __init__(self, height="400px", **files_widget_kwargs):
875
876
  super().__init__(_dom_classes=["KpathWidget"])
876
877
  self._fig = go.FigureWidget()
877
878
  self._sm = SelectMultiple(options=[], layout=Layout(width="auto"))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipyvasp
3
- Version: 0.8.0
3
+ Version: 0.8.1
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
@@ -32,7 +32,7 @@ Requires-Dist: nglview>=3.0.4; extra == "extra"
32
32
 
33
33
  # ipyvasp
34
34
 
35
- An successor of [pivotpy](https://github.com/massgh/pivotpy) for VASP-based DFT pre and post processing tool.
35
+ An VASP-based DFT pre and post processing tool.
36
36
 
37
37
  ## Install
38
38
  Currently the package is being built and not stable. If you want to use development version, install this way:(recommended to install in a virtual environment)
@@ -68,6 +68,9 @@ Interactively select bandstructure path by clicking on high symmetry points on p
68
68
 
69
69
  ![KP](KP.png)
70
70
 
71
- More coming soon!
71
+ Apply operations on POSCAR and simultaneously view using plotly's `FigureWidget` or `WeasWidget` in Jupyterlab side by side.
72
+
73
+ ![snip](op.png)
72
74
 
73
75
 
76
+ More coming soon!
@@ -1 +0,0 @@
1
- __version__ = "0.8.0"
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