ipyvasp 0.9.8__tar.gz → 0.9.82__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/PKG-INFO +1 -1
  2. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/_lattice.py +16 -8
  3. ipyvasp-0.9.82/ipyvasp/_version.py +1 -0
  4. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/core/serializer.py +10 -5
  5. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/core/spatial_toolkit.py +3 -0
  6. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/lattice.py +60 -7
  7. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp.egg-info/PKG-INFO +1 -1
  8. ipyvasp-0.9.8/ipyvasp/_version.py +0 -1
  9. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/LICENSE +0 -0
  10. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/README.md +0 -0
  11. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/__init__.py +0 -0
  12. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/__main__.py +0 -0
  13. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/_enplots.py +0 -0
  14. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/bsdos.py +0 -0
  15. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/cli.py +0 -0
  16. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/core/__init__.py +0 -0
  17. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/core/parser.py +0 -0
  18. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/core/plot_toolkit.py +0 -0
  19. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/evals_dataframe.py +0 -0
  20. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/misc.py +0 -0
  21. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/potential.py +0 -0
  22. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/utils.py +0 -0
  23. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp/widgets.py +0 -0
  24. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp.egg-info/SOURCES.txt +0 -0
  25. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp.egg-info/dependency_links.txt +0 -0
  26. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp.egg-info/entry_points.txt +0 -0
  27. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp.egg-info/requires.txt +0 -0
  28. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/ipyvasp.egg-info/top_level.txt +0 -0
  29. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/setup.cfg +0 -0
  30. {ipyvasp-0.9.8 → ipyvasp-0.9.82}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipyvasp
3
- Version: 0.9.8
3
+ Version: 0.9.82
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
@@ -1572,6 +1572,7 @@ def _get_bond_length(poscar_data, bond_length=None):
1572
1572
  class _Atom(NamedTuple):
1573
1573
  "Object passed to POSCAR operations `func` where atomic sites are modified. Additinal property p -> array([x,y,z])."
1574
1574
  symbol : str
1575
+ number : int
1575
1576
  index : int
1576
1577
  x : float
1577
1578
  y : float
@@ -1590,14 +1591,14 @@ class _AtomLabel(str):
1590
1591
 
1591
1592
  def _validate_func(func):
1592
1593
  if not callable(func):
1593
- raise ValueError("`func` must be a callable function with single parameter `Atom(symbol,index,x,y,z)`.")
1594
+ raise ValueError("`func` must be a callable function with single parameter `Atom(symbol,number, index,x,y,z)`.")
1594
1595
 
1595
1596
  if len(inspect.signature(func).parameters) != 1:
1596
1597
  raise ValueError(
1597
- "`func` takes exactly 1 argument: `Atom(symbol,index,x,y,z)` in fractional coordinates"
1598
+ "`func` takes exactly 1 argument: `Atom(symbol, number, index,x,y,z)` in fractional coordinates"
1598
1599
  )
1599
1600
 
1600
- ret = func(_Atom('',0,0,0,0))
1601
+ ret = func(_Atom('',0,0,0,0,0))
1601
1602
  if not isinstance(ret, (bool, np.bool_)):
1602
1603
  raise ValueError(
1603
1604
  f"`func` must be a function that returns a bool, got {type(ret)}."
@@ -1611,7 +1612,7 @@ def _masked_data(poscar_data, func):
1611
1612
  pick = []
1612
1613
  for i, pos in enumerate(poscar_data.positions):
1613
1614
  idx = eqv_inds[i] if eqv_inds else i # map to original index
1614
- if func(_Atom(poscar_data.symbols[i], idx, *pos)): # symbols based on i, not eqv_idx
1615
+ if func(_Atom(*poscar_data._sn[i], idx, *pos)): # labels based on i, not eqv_idx
1615
1616
  pick.append(i)
1616
1617
  return pick # could be duplicate indices
1617
1618
 
@@ -1633,10 +1634,13 @@ def _filter_pairs(labels, pairs, dist, bond_length):
1633
1634
  return pairs # None -> auto calculate bond_length, number -> use that number
1634
1635
 
1635
1636
  def filter_atoms(poscar_data, func, tol = 0.01):
1636
- """Filter atomic sites based on a function that acts on an atom such as `lambda a: (a.p < 1/2).all()`. `p` is additional property to get array([x,y,z]) togther.
1637
+ """Filter atomic sites based on a function that acts on an atom such as `lambda a: (a.p < 1/2).all()`.
1638
+ `atom` passed to function is a namedtuple like `Atom(symbol,number,index,x,y,z)` which has extra attribute `p = array([x,y,z])`.
1637
1639
  This may include equivalent sites, so it should be used for plotting purpose only, e.g. showing atoms on a plane.
1638
1640
  An attribute `source_indices` is added to metadata which is useful to pick other things such as `OUTCAR.ion_pot[POSCAR.filter(...).data.metadata.source_indices]`.
1639
1641
 
1642
+ >>> filter_atoms(..., lambda a: a.symbol=='Ga' or a.number in range(2)) # picks all Ga atoms and first two atoms of every other types.
1643
+
1640
1644
  Note: If you are filtering a plane with more than one non-zero hkl like 110, you may first need to translate or set boundary on POSCAR to bring desired plane in full view to include all atoms.
1641
1645
  """
1642
1646
  if hasattr(poscar_data.metadata, 'source_indices'):
@@ -1931,7 +1935,8 @@ def splot_lattice(
1931
1935
  bond_kws : dict
1932
1936
  Keyword arguments to pass to `LineCollection`/`Line3DCollection` for plotting bonds.
1933
1937
  fmt_label : callable
1934
- If given, each site label is passed to it as a subclass of str 'Ga 1' with extra attributes `symbol` and `number` and a method `to_latex`, e.g. `lambda lab: lab.to_latex()`.
1938
+ If given, each site label is passed to it as a subclass of str 'Ga 1' with extra attributes `symbol` and `number` and a method `to_latex`.
1939
+ You can show specific labels based on condition, e.g. `lambda lab: lab.to_latex() if lab.number in [1,5] else ''` will show 1st and 5th atom of each types.
1935
1940
  It must return a string or a list/tuple of length 2 with first item as label and second item as dictionary of keywords to pass to `plt.text`.
1936
1941
  plot_cell : bool
1937
1942
  Default is True, plot unit cell with default settings.
@@ -2583,9 +2588,12 @@ def sort_poscar(poscar_data, new_order):
2583
2588
  return serializer.PoscarData(data)
2584
2589
 
2585
2590
  def remove_atoms(poscar_data, func, fillby=None):
2586
- """Remove atoms that satisfy `func(atom) -> bool` on their fractional coordinates like `lambda a: all(a.p < 1/2)`. `p` is additional property to get array([x,y,z]) togther.
2591
+ """Remove atoms that satisfy `func(atom) -> bool` on their fractional coordinates like `lambda a: all(a.p < 1/2)`.
2592
+ `atom` passed to function is a namedtuple like `Atom(symbol,number,index,x,y,z)` which has extra attribute `p = array([x,y,z])`.
2587
2593
  If `fillby` is given, it will fill the removed atoms with atoms from fillby POSCAR.
2588
2594
 
2595
+ >>> remove_atoms(..., lambda a: sum((a.p - 0.5)**2) <= 0.25**2) # remove atoms in center of cell inside radius of 0.25
2596
+
2589
2597
  .. note::
2590
2598
  The coordinates of fillby POSCAR are transformed to basis of given POSCAR, before filling.
2591
2599
  So a good filling is only guaranteed if both POSCARs have smaller lattice mismatch.
@@ -2612,7 +2620,7 @@ def remove_atoms(poscar_data, func, fillby=None):
2612
2620
 
2613
2621
  def keep_pos(i, x, y, z): # keep positions in basis of given data
2614
2622
  u, v, w = to_basis(poscar_data.basis, to_R3(fillby.basis, [[x, y, z]]))[0]
2615
- return bool(func(_Atom('', 0, u, v, w)))
2623
+ return bool(func(_Atom('', 0, 0, u, v, w)))
2616
2624
 
2617
2625
  mask = _masked_data(fillby, keep_pos)
2618
2626
  N_prev = len(data["positions"]) # before filling
@@ -0,0 +1 @@
1
+ __version__ = "0.9.82"
@@ -329,6 +329,10 @@ class PoscarData(Dict2Data):
329
329
  "Returns the symbols of the atoms in the poscar data without numbers"
330
330
  return np.array([lab.split()[0] for lab in self.labels])
331
331
 
332
+ @property
333
+ def _sn(self): # symbol and number
334
+ return np.array([[f(v) for f,v in zip((str,int),lab.split())] for lab in self.labels],dtype=object)
335
+
332
336
  @property
333
337
  def sites(self):
334
338
  "Returns data with types mapped to their positions."
@@ -493,7 +497,8 @@ class PoscarData(Dict2Data):
493
497
  """
494
498
  Returns a dictionary of {'Ga 1': 'T T T', 'As 1': 'T F F',...} for each atom in the poscar data.
495
499
 
496
- `func` should be a callable like `func(Atom(symbol,index,x,y,z)) -> (bool, bool, bool)` which turns on/off selective dynamics for each atom based in each dimension.
500
+ `func` should be a callable like `func(atom) -> (bool, bool, bool)` which turns on/off selective dynamics for each atom based in each dimension.
501
+ `atom` passed to function is a namedtuple like `Atom(symbol,number,index,x,y,z)` which has extra attribute `p = array([x,y,z])`.
497
502
 
498
503
  You can visualize selective dynamics sites by their labels as follows:
499
504
 
@@ -504,17 +509,17 @@ class PoscarData(Dict2Data):
504
509
  """
505
510
  if not callable(func):
506
511
  raise TypeError(
507
- "`func` should be a callable with one paramter `Atom(symbol,index, x,y,z)`"
512
+ "`func` should be a callable with one paramter `Atom(symbol,number, index, x,y,z)`"
508
513
  )
509
514
 
510
515
  if len(inspect.signature(func).parameters) != 1:
511
516
  raise ValueError(
512
- "`func` should be a callable function with one paramter `Atom(symbol,index, x,y,z)` in fractional coordinates."
517
+ "`func` should be a callable function with one paramter `Atom(symbol,number, index, x,y,z)` in fractional coordinates."
513
518
  )
514
519
 
515
520
  from .._lattice import _Atom # avoids circular import
516
521
 
517
- test_output = func(_Atom('',0, 0, 0, 0))
522
+ test_output = func(_Atom('',0, 0, 0, 0, 0))
518
523
  if (
519
524
  not isinstance(test_output, (list, tuple, np.ndarray))
520
525
  or len(test_output) != 3
@@ -530,7 +535,7 @@ class PoscarData(Dict2Data):
530
535
  )
531
536
 
532
537
  sd_list = [
533
- " ".join("T" if s else "F" for s in func(_Atom(self.symbols[i],i, *p)))
538
+ " ".join("T" if s else "F" for s in func(_Atom(*self._sn[i],i, *p)))
534
539
  for i, p in enumerate(self.positions)
535
540
  ]
536
541
  labels = np.array(
@@ -315,6 +315,9 @@ def get_bz(basis, loop=True, primitive=False):
315
315
  "vertices": verts,
316
316
  "faces": idx_faces,
317
317
  "primitive": primitive,
318
+ "edges": np.unique(
319
+ np.sort([[i,j] for f in idx_faces for i,j in zip(f[:-1],f[1:])], axis=1),
320
+ axis=0)
318
321
  }
319
322
  from .serializer import BrZoneData # to avoid circular import
320
323
 
@@ -18,11 +18,13 @@ from contextlib import suppress
18
18
  import numpy as np
19
19
  from pandas.io.clipboard import clipboard_get, clipboard_set
20
20
  import matplotlib.colors as mcolors
21
+ from mpl_toolkits.mplot3d.art3d import Poly3DCollection
22
+ import plotly.graph_objects as go
21
23
 
22
24
 
23
25
  from .core import serializer
24
26
  from .core import spatial_toolkit as stk
25
- from .core.plot_toolkit import iplot2widget
27
+ from .core.plot_toolkit import get_axes, iplot2widget
26
28
  from .utils import _sig_kwargs, _sub_doc
27
29
  from . import _lattice as plat
28
30
  from ._lattice import (
@@ -313,13 +315,13 @@ class POSCAR:
313
315
 
314
316
  Prefrence order: data > content > path
315
317
 
316
- Note: During chained operations where functions are acting on index of sites, use `self.last` instead of `self` to point to latest POSCAR in chain.
318
+ Note: POSCAR operations that need a `func` accept basis, atom tuple, label etc. Read their documentation.
317
319
 
318
320
  ```python
319
321
  pc = POSCAR()
320
- pc.filter_atoms(lambda a: a.index in pc.data.types.Ga) # FINE
321
- pc.set_boundary([-2,2]).filter_atoms(lambda a: a.index in pc.data.types.Ga) # INCORRECT sites picked
322
- pc.set_boundary([-2,2]).filter_atoms(lambda a: a.index in pc.last.data.types.Ga) # PERFECT, pc.last is output of set_boundary
322
+ pc.filter_atoms(lambda a: a.symbol == 'Ga') # a is namedtuple `Atom(symbol,number,index,x,y,z)` which has extra attribute `p = array([x,y,z])`.
323
+ pc.transform(lambda a,b,c: (a+b,a-b,c)) # basis or transform matrix
324
+ pc.splot_lattice(lambda lab: lab.to_latex()) # lab is str subclass like `AtomLabel('Ga 1')` with extra attributes `symbol,number, to_latex()` that can be used to show specific sites labels only.
323
325
  ```
324
326
 
325
327
  Tip: You can use `self.auto_renderer.on()` to keep doing opertions and visualize while last line of any cell is a POSCAR object.
@@ -354,11 +356,11 @@ class POSCAR:
354
356
 
355
357
  @property
356
358
  def last(self):
357
- """Points to last created POSCAR instance during chained operations!
359
+ """Points to last created POSCAR instance during chained operations! You don't need to store results.
358
360
 
359
361
  ```python
360
362
  pc = POSCAR()
361
- pc.filter_atoms(lambda a: a.index in pc.data.types.Ga) # FINE
363
+ pc.filter_atoms(lambda a: a.index in pc.data.types.Ga) # FINE, can use a.symbol == 'Ga' too, but we need to show a point below
362
364
  pc.set_boundary([-2,2]).filter_atoms(lambda a: a.index in pc.data.types.Ga) # INCORRECT sites picked
363
365
  pc.set_boundary([-2,2]).filter_atoms(lambda a: a.index in pc.last.data.types.Ga) # PERFECT, pc.last is output of set_boundary
364
366
  ```
@@ -626,6 +628,57 @@ class POSCAR:
626
628
  if not hasattr(self, "_cell"):
627
629
  self._cell = self.get_cell()
628
630
  return self._cell
631
+
632
+ def get_plane(self, hkl, d=1/2,tol=1e-2):
633
+ """Returns Nx3 vertices of a plane bound inside cell. .
634
+ hkl should be list of three miller indices. d is fractional distance in range 0,1 in direction of hkl.
635
+ e.g. if there are 8 planes of atoms in a cubic cell, d = 0, 1/8,...7/8, 1 match position of those planes.
636
+ """
637
+ from sympy import Point3D, Line3D, Plane
638
+ V = self.data.rec_basis.dot(hkl)
639
+ normal = V/np.linalg.norm(V)
640
+ point = d*normal*np.linalg.norm(self.data.basis.dot(hkl)) # to make d 0-1
641
+ P = Plane(Point3D(*point),normal_vector=normal)
642
+
643
+ pts = []
644
+ for e in self.cell.vertices[self.cell.edges]:
645
+ L = Line3D(Point3D(*e[0]),Point3D(*e[1]))
646
+ if (isc := P.intersection(L)) and isinstance(isc[0],Point3D):
647
+ pts.append(isc[0])
648
+
649
+ pts = np.unique(np.array(pts,dtype=float),axis=0)
650
+ pts = pts[stk.order(pts,)]
651
+ qts = self.cell.to_fractional(pts)
652
+ qts = qts[(qts >= -tol).all(axis=1) & (qts <= 1 + tol).all(axis=1)]
653
+ pts = self.cell.to_cartesian(qts)
654
+ return pts
655
+
656
+ def splot_plane(self, hkl, d=1/2,tol=1e-2,ax=None, **kwargs):
657
+ """Provide hkl and a 3D axes to plot plane. kwargs are passed to `mpl_toolkits.mplot3d.art3d.Poly3DCollection`
658
+ Note: You may get wrong plane if your basis are not aligned to axes. So you can use `transpose` or `set_zdir` methods before plottling cell.
659
+ """
660
+ P = self.get_plane(hkl,d=d,tol=tol)
661
+ if ax is None:
662
+ ax = get_axes(axes_3d=True)
663
+ ax.set( # it does not show otherwise
664
+ xlim=[P[:,0].min(),P[:,0].max()],
665
+ ylim=[P[:,1].min(),P[:,1].max()],
666
+ zlim=[P[:,2].min(),P[:,2].max()]
667
+ )
668
+ kwargs = {'alpha':0.5,'color':'#898','shade': False, **kwargs}
669
+ ax.add_collection(Poly3DCollection([P],**kwargs))
670
+ ax.autoscale_view()
671
+ return ax
672
+
673
+ def iplot_plane(self, hkl, d = 1/2, tol=1e-3, fig=None,**kwargs):
674
+ "Plot plane on a plotly Figure. kwargs are passed to `plotly.graph_objects.Mesh3d`."
675
+ if fig is None:
676
+ fig = go.Figure()
677
+
678
+ P = self.get_plane(hkl,d=d,tol=tol)
679
+ kwargs = {**dict(color='#8a8',opacity=0.7,alphahull=0, showlegend=True,name=str(hkl)),**kwargs}
680
+ fig.add_trace(go.Mesh3d({k:v for v,k in zip(P.T, 'xyz')},**kwargs))
681
+ return fig
629
682
 
630
683
  @_sub_doc(stk.get_bz, {"basis :.*loop :": "loop :"})
631
684
  @_sig_kwargs(stk.get_bz, ("basis",))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ipyvasp
3
- Version: 0.9.8
3
+ Version: 0.9.82
4
4
  Summary: A processing tool for VASP DFT input/output processing in Jupyter Notebook.
5
5
  Home-page: https://github.com/massgh/ipyvasp
6
6
  Author: Abdul Saboor
@@ -1 +0,0 @@
1
- __version__ = "0.9.8"
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
File without changes
File without changes
File without changes