__all__ = [
"POSCAR",
"download_structure",
"periodic_table",
"get_kpath",
"get_kmesh",
"splot_bz",
"iplot_bz",
"ngl_viewer",
]
from pathlib import Path
from contextlib import redirect_stdout
from io import StringIO
from itertools import permutations
from contextlib import suppress
from collections import namedtuple
import numpy as np
from pandas.io.clipboard import clipboard_get, clipboard_set
import matplotlib.colors as mcolors
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import plotly.graph_objects as go
from .core import serializer
from .core import spatial_toolkit as stk
from .core.plot_toolkit import get_axes, iplot2widget
from .utils import _sig_kwargs, _sub_doc
from . import _lattice as plat
from ._lattice import (
periodic_table,
get_kpath,
get_kmesh,
splot_bz,
iplot_bz,
) # need these as direct access
[docs]
def download_structure(
formula, mp_id=None, max_sites=None, min_sites=None, api_key=None, save_key=False
):
"""
Download structure data from Materials project website.
Parameters
----------
formula : chemical formula of the material.
mp_id : Materials project id of material.
max_sites : maximum number of sites in structure to download.
min_sites : minimum number of sites in structure to download.
Other Parameters
----------------
api_key : API key from Materials project websit, if you use save_key=True, never required again.
save_key : Save API key to file. You can save any time of key or device changed.
Return
------
list : List of Structure data containing attribute/method `cif`/`export_poscar, write_cif` etc.
.. note::
max_sites and min_sites are used to filter the number of sites in structure, or use mp_id to download a specific structure.
"""
mp = plat.InvokeMaterialsProject(api_key=api_key)
output = mp.request(
formula=formula, mp_id=mp_id, max_sites=max_sites, min_sites=min_sites
) # make a request
if save_key and isinstance(api_key, str):
mp.save_api_key(api_key)
if mp.success:
return output
else:
raise ConnectionError("Connection was not sccessful. Try again!")
[docs]
def ngl_viewer(
poscar,
colors=None,
sizes=0.5,
plot_cell=True,
linewidth=0.05,
color=[0, 0, 0.2],
bond_color="whitesmoke",
width="400px",
height="400px",
plot_vectors=True,
dashboard=False,
origin=(0, 0, 0),
eqv_sites = True,
):
"""Display structure in Jupyter notebook using nglview.
Parameters
----------
poscar : ipyvasp.POSCAR
sizes : float or dict of type -> float
Size of sites. Either one int/float or a mapping like {'Ga': 2, ...}.
colors : color or sheme name or dict of type -> color
Mapping of colors like {'Ga': 'red, ...} or a single color. Automatically generated color for missing types.
If `colors = 'element'`, then element colors from `nglview` will be used.
You can use `nglview.color.COLOR_SCHEME` to see available color schemes to use.
plot_cell : bool
Plot unit cell. Default True.
linewidth : float
Linewidth of cell edges.
color : list or str
Color of cell edges. Must be a valid color to support conversion to RGB via matplotlib.
bond_color : str
Color of bonds. Default "whitesmoke". Can be "element" or any other scheme from `nglview`.
width : str
Width of viewer. Default "400px".
height : str
Height of viewer. Default "400px".
plot_vectors : bool
Plot vectors. Default True. Only works if `plot_cell = True`.
dashboard : bool
Show dashboard. Default False. It just sets `view.gui_style = 'NGL'`.
Returns
-------
NGLWidget
An instance of nglview.NGLWidget object. You can use `.render_image()`, `.download_image()` etc. on it or can add more representations.
.. note::
`nglview` sometimes does not work in Jupyter lab. You can switch to classic notebook in that case.
.. tip::
See `Advanced NGLView usage <https://projects.volkamerlab.org/teachopencadd/talktorials/T017_advanced_nglview_usage.html>`_.
"""
if not isinstance(poscar, POSCAR):
raise TypeError("poscar must be an instance of POSCAR class.")
try:
import nglview as nv
except ImportError:
raise ImportError("Please install nglview to use this function.")
# Only show equivalent sites if plotting cell, only shift origin otherwise
poscar = POSCAR( # don't change instance itself, make new one
data=plat._fix_sites(poscar.data, eqv_sites=eqv_sites, origin=origin)
)
_types = poscar.data.types.to_dict()
_fcs = plat._fix_color_size(_types,colors,sizes,0.5, backend='ngl')
_sizes = [v['size'] for v in _fcs.values()]
_colors = [v['color'] for v in _fcs.values()]
view = nv.NGLWidget(
nv.ASEStructure(poscar.to_ase()),
width=width,
height=height,
default_representation=False,
)
view.clear()
for e, r, c in zip(_types, _sizes, _colors):
view.add_spacefill(radius=r, selection=f"#{e}", color=c)
view.add_ball_and_stick(color=bond_color)
view.camera = "orthographic"
view.center()
if plot_cell:
# arrays.tolist() is important for nglview to write json
shape = nv.shape.Shape(view=view)
_color = mcolors.to_rgb(color) # convert to rgb for nglview
cell = poscar.get_cell()
for face in cell.faces_coords:
for p1, p2 in zip(face[1:], face[:-1]):
shape.add_cylinder(p1.tolist(), p2.tolist(), _color, linewidth)
for v in cell.vertices:
shape.add_sphere(v.tolist(), _color, linewidth)
if plot_vectors:
for i, b in enumerate(cell.basis, start=1):
tail = b - b / np.linalg.norm(b)
shape.add_cone(
tail.tolist(), b.tolist(), _color, linewidth * 3, f"a{i}"
)
if dashboard:
view.gui_style = "NGL"
return view
def weas_viewer(poscar,
sizes=1,
colors=None,
bond_length=None,
model_style = 1,
plot_cell=True,
origin = (0,0,0),
eqv_sites = True,
):
"""
sizes : float or dict of type -> float
Size of sites. Either one int/float or a mapping like {'Ga': 2, ...}.
colors : color, color scheme or dict of type -> color
Mapping of colors like {'Ga': 'red, ...} or a single color. Automatically generated color for missing types.
You can use color schemes as 'VESTA','JMOL','CPK'.
sizes : list
List of sizes for each atom type.
model_type: int
whether to show Balls (0), Ball + Stick (1), Polyheda (2) or Sticks (3).
plot_cell : bool
Plot unit cell. Default True.
bond_length : float or dict
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.
Returns a WeasWidget instance. You can use `.export_image`, `save_image` and other operations on it.
Read what you can do more with `WeasWidget` [here](https://weas-widget.readthedocs.io/en/latest/index.html).
"""
from weas_widget import WeasWidget
if len(poscar.data.positions) < 1:
raise ValueError("Need at least 1 atom!")
# Only show equivalent sites if plotting cell, only shift origin otherwise
poscar = POSCAR( # don't change instance itself, make new one
data=plat._fix_sites(poscar.data, eqv_sites=eqv_sites, origin=origin)
)
w = WeasWidget(from_ase=poscar.to_ase())
w.avr.show_bonded_atoms = False if plot_cell else True # plot_cell fix atoms itself
w.avr.model_style = model_style
w.avr.show_cell = plot_cell
if bond_length:
if isinstance(bond_length,(int,float)):
for a,b in permutations(list(poscar.data.types.keys()),2):
with suppress(KeyError):
w.avr.bond.settings[f'{a}-{b}'].update({'max': bond_length})
elif isinstance(bond_length, dict):
for key, value in bond_length.items():
w.avr.bond.settings[key].update({'max': value})
_fcs = plat._fix_color_size(poscar.data.types.to_dict(), colors, sizes, 1)
for key, value in _fcs.items():
w.avr.species.settings[key].update({"radius": value["size"]})
if isinstance(colors, str) and colors in ['VESTA','JMOL','CPK']:
w.avr.color_type = colors
else:
colors = {key: value["color"] for key, value in _fcs.items()}
for key,value in colors.items():
w.avr.species.settings[key].update({"color": value})
for (k1,c1), (k2,c2) in permutations(colors.items(),2):
with suppress(KeyError):
w.avr.bond.settings[f'{k1}-{k2}'].update({'color1':c1,'color2':c2})
return w
class _AutoRenderer:
_figw = None
_kws = {}
def __init__(self, pc_cls):
self._pc = pc_cls
def on(self, template=None):
"Enable auto rendering. In Jupyterlab, you can use `Create New View for Output` to drag a view on side."
self.off()
type(self)._figw = iplot2widget(self._pc._last.iplot_lattice(**self._kws), fig_widget=self._figw,template=template)
def ip_display(that):
iplot2widget(that.iplot_lattice(**self._kws), fig_widget=self._figw, template=template)
self._pc._ipython_display_ = ip_display
from ipywidgets import Button, VBox
btn = Button(description='Disable Auto Rendering',icon='close',layout={'width': 'max-content'})
btn.on_click(lambda btn: self.off())
type(self)._box = VBox([btn, self._figw])
return display(self._box)
def off(self):
"Disable auto rendering."
if hasattr(self, '_box'):
self._box.close()
type(self)._figw = None # no need to close figw, it raise warning, but now garbage collected
if hasattr(self._pc, '_ipython_display_'):
del self._pc._ipython_display_
@_sig_kwargs(plat.iplot_lattice,('poscar_data',))
def update_params(self, **kwargs):
type(self)._kws = kwargs
if hasattr(self._pc, '_ipython_display_'):
self._pc._last._ipython_display_()
@property
def params(self):
return self._kws.copy() # avoid messing original
def __repr__(self):
return f"AutoRenderer(params = {self._kws})"
[docs]
class POSCAR:
_cb_instance = {} # Loads last clipboard data if not changed
_mp_instance = {} # Loads last mp data if not changed
def __init__(self, path=None, content=None, data=None):
"""
POSCAR class to contain data and related methods, data is PoscarData, json/tuple file/string.
Parameters
----------
path : path to file
content : string of POSCAR content
data : PoscarData object. This assumes positions are in fractional coordinates.
Prefrence order: data > content > path
Note: POSCAR operations that need a `func` accept basis, atom tuple, label etc. Read their documentation.
```python
pc = POSCAR()
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])`.
pc.transform(lambda a,b,c: (a+b,a-b,c)) # basis or transform matrix
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.
```
Tip: You can use `self.auto_renderer.on()` to keep doing opertions and visualize while last line of any cell is a POSCAR object.
"""
self._path = Path(path or "POSCAR") # Path to file
self._content = content
self.__class__._last = self # need this to access in lambda in chain operations
if data:
self._data = serializer.PoscarData.validated(data)
else:
self._data = plat.export_poscar(path=str(self.path), content=content)
def __repr__(self):
atoms = ", ".join([f"{k}={len(v)}" for k, v in self.data.types.items()])
lat = ", ".join(
[
f"{k}={v}"
for k, v in zip(
['a','b','c','alpha','beta','gamma'], (*self.data.norms.round(3), *self.data.angles.round(3))
)
]
)
return f"{self.__class__.__name__}({atoms}, {lat})"
def __str__(self):
return self.content
@property
def path(self):
return self._path
@property
def last(self):
"""Points to last created POSCAR instance during chained operations! You don't need to store results.
```python
pc = POSCAR()
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
pc.set_boundary([-2,2]).filter_atoms(lambda a: a.index in pc.data.types.Ga) # INCORRECT sites picked
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
```
"""
return self._last
@property
def auto_renderer(self):
"""A renderer for auto viewing POSCAR when at last line of cell.
Use `auto_renderer.on()` to enable it.
Use `auto_renderer.off()` to disable it.
Use `auto_renderer.[params, update_params()]` to view and update parameters.
In Jupyterlab, you can use `Create New View for Output` to drag a view on side.
In VS Code, you can open another view of Notebook to see it on side while doing operations.
"""
if not hasattr(self, '_renderer'):
self.__class__._renderer = _AutoRenderer(self.__class__) # assign to class
return self._renderer
[docs]
def to_ase(self):
"""Convert to ase.Atoms format. You need to have ase installed.
You can apply all ase methods on this object after conversion.
Example
-------
>>> from ase.visualize import view
>>> structure = poscar.to_ase()
>>> view(structure) # POSCAR.view() also uses this method if viewer is given.
>>> reciprocal_lattice = structure.cell.get_bravais_lattice()
>>> reciprocal_lattice.plt_bz() # Plot BZ usinn ase, it also plots suggested band path.
"""
from ase import Atoms
return Atoms( # ASE positions are cartesian, not fractional
symbols=self.data.symbols, positions=self.data.coords, cell=self.data.basis
)
[docs]
def view(self, viewer=None, **kwargs):
"""View POSCAR in notebook. If viewer is given it will be passed ase.visualize.view. You need to have ase installed.
kwargs are passed to self.splot_lattice if viewer is None, otherwise a single keyword argument `data` is passed to ase viewer.
data should be volumetric data for ase.visualize.view, such as charge density, spin density, etc.
.. tip::
Use ``self.view_ngl()`` if you don't want to pass ``viewer = 'nglview'`` to ASE viewer or not showing volumetric data.
"""
if viewer is None:
return plat.view_poscar(self.data, **kwargs)
elif viewer in "weas":
return weas_viewer(self, **kwargs)
elif viewer in "plotly":
return self.view_plotly(**kwargs)
elif viewer in "nglview":
return print(
f"Use `self.view_ngl()` for better customization in case of viewer={viewer!r}"
)
else:
from ase.visualize import view
return view(self.to_ase(), viewer=viewer, data=kwargs.get("data", None))
[docs]
@_sub_doc(ngl_viewer, {"poscar :.*colors :": "colors :"})
@_sig_kwargs(ngl_viewer, ("poscar",))
def view_ngl(self, **kwargs):
return ngl_viewer(self, **kwargs)
[docs]
@_sub_doc(weas_viewer)
@_sig_kwargs(weas_viewer, ("poscar",))
def view_weas(self, **kwargs):
return weas_viewer(self, **kwargs)
[docs]
def view_kpath(self, height='400px'):
"Initialize a KPathWidget instance to view kpath for current POSCAR, and you can select others too."
from .widgets import KPathWidget
return KPathWidget([self.path,],height=height)
[docs]
@_sub_doc(plat.iplot_lattice)
@_sig_kwargs(plat.iplot_lattice, ("poscar_data",))
def view_plotly(self, **kwargs):
return iplot2widget(self.iplot_lattice(**kwargs))
[docs]
@classmethod
def from_file(cls, path):
"Load data from POSCAR file"
return cls(path=path)
[docs]
@classmethod
def from_string(cls, content):
"content should be a valid POSCAR string"
try:
return cls(content=content)
except:
raise ValueError(f"Invalid POSCAR string!!!!!\n{content}")
[docs]
@classmethod
def download(cls, formula, mp_id, api_key=None, save_key=False):
"""Downloads POSCAR from materials project. `mp_id` should be string associated with a material on their website. `api_key` is optional if not saved.
Get your API key from https://legacy.materialsproject.org/open and save it using `save_key` to avoid entering it everytime.
"""
if cls._mp_instance and cls._mp_instance["kwargs"] == {
"formula": formula,
"mp_id": mp_id,
}:
if (
api_key and save_key
): # If user wants to save key even if data is loaded from cache
plat._save_mp_API(api_key)
return cls._mp_instance["instance"]
instance = cls(
data=download_structure(
formula=formula, mp_id=mp_id, api_key=api_key, save_key=save_key
)[0].export_poscar()
)
cls._mp_instance = {
"instance": instance,
"kwargs": {"formula": formula, "mp_id": mp_id},
}
return instance
[docs]
@classmethod
def from_cif(cls, path):
"Load data from cif file"
p = Path(path)
if not p.is_file():
raise FileNotFoundError(f"File {path!r} does not exists")
with p.open("r", encoding="utf-8") as f:
cif_str = f.read()
poscar_content = plat._cif_str_to_poscar_str(
cif_str, "Exported from cif file using ipyvasp"
)
return cls.from_string(poscar_content)
[docs]
def to_dict(self):
"Returns a dictionary that can be modified generally and passed to `POSCAR.new` method."
data = self.data.copy() # avoid overwriting numpy arrays
out = {"system": data.SYSTEM, "basis": data.basis}
out["sites"] = {k: data.positions[v] for k, v in data.types.items()}
return out
[docs]
@classmethod
def new(cls, basis, sites, scale=1, system=None):
"""
Crate a new POSCAR instance from basis and sites.
Parameters
----------
basis : array_like of shape (3, 3)
sites : dict, key is element and value is array_like of shape (n, 3)
For example, {'Mg': [[0, 0, 0]], 'Cl': [[1/3, 2/3,0.214],[2/3,1/3,0.786]]} for MgCl2.
scale : int or float used to scale basis and kept in metadata to use when writing to file.
system : str, name of the structure, if None, will be inferred from sites.
"""
if np.ndim(basis) != 2 and np.shape(basis) != (3, 3):
raise ValueError("basis should be a 3x3 array")
if not isinstance(scale, (int, float, np.integer)):
raise ValueError("scale should be a number")
basis = np.array(basis) * scale # scale basis, we add scale to metdata later
if not isinstance(sites, dict):
raise ValueError(
"sites should be a dictionary of elements and their positions"
)
type_dict, positions, start = {}, [], 0
for key, value in sites.items():
if not isinstance(key, str):
raise ValueError("sites key should be a string like 'Al'")
if np.ndim(value) != 2 and np.shape(value)[1] != 3:
raise ValueError("sites value should be an array of shape (n, 3)")
if np.max(value) > 1.01 or np.min(value) < -0.01:
raise ValueError(
"sites value should be in fractional coordinates between 0 and 1"
)
type_dict[key] = range(start, start + len(value))
positions.extend(value) # direct stacking of positions
start += len(value)
positions = np.array(positions)
out_dict = {
"SYSTEM": system if system else "".join(type_dict.keys()),
"basis": basis,
"metadata": {
"scale": scale,
"cartesian": False,
"comment": "Created using ipyvasp.lattice.POSCAR.new method",
},
"positions": positions,
"types": type_dict,
}
return cls(data=out_dict)
[docs]
@classmethod
def from_clipborad(cls):
"Read POSCAR from clipboard (based on clipboard reader impelemented by pandas library) It picks the latest from clipboard."
try:
instance = cls.from_string(
content=clipboard_get()
) # read from clipboard while allowing error for bad data
if isinstance(instance, cls): # if valid POSCAR string
cls._cb_instance = {"instance": instance} # cache instance
return instance
except:
if cls._cb_instance:
print(
"Loading from previously cached clipboard data, as current data is not valid POSCAR string."
)
return cls._cb_instance["instance"]
else:
raise ValueError(
"Clipboard does not contain valid POSCAR string and no previous data is cached."
)
[docs]
def to_clipboard(self):
"Writes POSCAR to clipboard (as implemented by pandas library) for copy in other programs such as vim."
clipboard_set(self.content) # write to clipboard
@property
def data(self):
"Data object in POSCAR."
return self._data
@property
def metadata(self):
"Metadata associated with this POSCAR."
return self._data.metadata
[docs]
def copy(self):
"Copy POSCAR object. It avoids accidental changes to numpy arrays in original object."
return self.__class__(data=self.data.copy())
[docs]
@_sub_doc(plat.sort_poscar)
def sort(self, new_order):
return self.__class__(data=plat.sort_poscar(self.data, new_order))
@property
def content(self):
"POSCAR content."
with redirect_stdout(StringIO()) as f:
self.write(outfile=None) # print to stdout
return f.getvalue()
@property
def bz(self):
"Regular BZ data. Shortcut for `get_bz(primitive = False)`."
if not hasattr(self, "_bz"):
self._bz = self.get_bz(primitive=False)
return self._bz
@property
def pbz(self):
"Primitive BZ data. Shortcut for `get_bz(primitive = True)`."
return self.get_bz(primitive=True)
@property
def cell(self):
if not hasattr(self, "_cell"):
self._cell = self.get_cell()
return self._cell
[docs]
def get_plane(self, hkl, d=1/2,tol=1e-2):
"""Returns tuple `Plane(point, normal, vertices)` of a plane bound inside cell. .
hkl should be list of three miller indices. d is fractional distance in range 0,1 in direction of hkl.
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.
"""
from sympy import Point3D, Line3D, Plane
V = self.data.rec_basis.dot(hkl)
normal = V/np.linalg.norm(V)
point = d*normal*np.linalg.norm(self.data.basis.dot(hkl)) # to make d 0-1
P = Plane(Point3D(*point),normal_vector=normal)
pts = []
for e in self.cell.vertices[self.cell.edges]:
L = Line3D(Point3D(*e[0]),Point3D(*e[1]))
if (isc := P.intersection(L)) and isinstance(isc[0],Point3D):
pts.append(isc[0])
pts = np.unique(np.array(pts,dtype=float),axis=0)
pts = pts[stk.order(pts,)]
qts = self.cell.to_fractional(pts)
qts = qts[(qts >= -tol).all(axis=1) & (qts <= 1 + tol).all(axis=1)]
pts = self.cell.to_cartesian(qts)
return namedtuple("Plane","point normal vertices")(point, normal, pts)
[docs]
def splot_plane(self, hkl, d=1/2,tol=1e-2,ax=None, **kwargs):
"""Provide hkl and a 3D axes to plot plane. kwargs are passed to `mpl_toolkits.mplot3d.art3d.Poly3DCollection`
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.
"""
P = self.get_plane(hkl,d=d,tol=tol).vertices
if ax is None:
ax = get_axes(axes_3d=True)
ax.set( # it does not show otherwise
xlim=[P[:,0].min(),P[:,0].max()],
ylim=[P[:,1].min(),P[:,1].max()],
zlim=[P[:,2].min(),P[:,2].max()]
)
kwargs = {'alpha':0.5,'color':'#898','shade': False, 'label':str(hkl), **kwargs}
ax.add_collection(Poly3DCollection([P],**kwargs))
ax.autoscale_view()
return ax
[docs]
def iplot_plane(self, hkl, d = 1/2, tol=1e-3, fig=None,**kwargs):
"Plot plane on a plotly Figure. kwargs are passed to `plotly.graph_objects.Mesh3d`."
if fig is None:
fig = go.Figure()
P = self.get_plane(hkl,d=d,tol=tol)
kwargs['delaunayaxis'] = ('xyz')[np.abs(np.eye(3).dot(P.normal)).argmax()] # with alphahull=-1, delaunayaxis to be set properly
kwargs = {**dict(color='#8a8',opacity=0.7,alphahull=-1, showlegend=True,name=str(hkl)),**kwargs}
fig.add_trace(go.Mesh3d({k:v for v,k in zip(P.vertices.T, 'xyz')},**kwargs))
return fig
[docs]
@_sub_doc(stk.get_bz, {"basis :.*loop :": "loop :"})
@_sig_kwargs(stk.get_bz, ("basis",))
def get_bz(self, **kwargs):
return stk.get_bz(self.data.rec_basis, **kwargs)
[docs]
def get_cell(self, loop=True):
"See docs of `get_bz`, same except space is inverted and no factor of 2pi."
return serializer.CellData(
stk.get_bz( # data.basis makes prmitive cell in direct space
basis=self.data.basis, loop=loop, primitive=True
).to_dict()
) # cell must be primitive
[docs]
@_sub_doc(plat.splot_bz, {"bz_data :.*plane :": "plane :"})
@_sig_kwargs(plat.splot_bz, ("bz_data",))
def splot_bz(self, plane=None, **kwargs):
return plat.splot_bz(bz_data=self.bz, plane=plane, **kwargs)
[docs]
@_sub_doc(plat.splot_kpath)
@_sig_kwargs(plat.splot_kpath, ("bz_data",))
def splot_kpath(self, kpoints, **kwargs):
return plat.splot_kpath(self.bz, kpoints=kpoints, **kwargs)
[docs]
@_sig_kwargs(plat.splot_bz, ("bz_data",))
def splot_cell(self, plane=None, **kwargs):
"See docs of `splot_bz`, everything is same except space is inverted."
return plat.splot_bz(bz_data=self.cell, plane=plane, **kwargs)
[docs]
@_sub_doc(plat.iplot_bz, {"bz_data :.*fill :": "fill :"})
@_sig_kwargs(plat.iplot_bz, ("bz_data",))
def iplot_bz(self, **kwargs):
return plat.iplot_bz(bz_data=self.bz, **kwargs)
[docs]
@_sig_kwargs(plat.iplot_bz, ("bz_data", "special_kpoints"))
def iplot_cell(self, **kwargs):
"See docs of `iplot_bz`, everything is same except space is iverted."
return plat.iplot_bz(bz_data=self.cell, special_kpoints=False, **kwargs)
[docs]
@_sub_doc(plat.splot_lattice)
@_sig_kwargs(plat.splot_lattice, ("poscar_data", "plane"))
def splot_lattice(self, plane=None, **kwargs):
return plat.splot_lattice(self.data, plane=plane, **kwargs)
[docs]
@_sub_doc(plat.iplot_lattice)
@_sig_kwargs(plat.iplot_lattice, ("poscar_data",))
def iplot_lattice(self, **kwargs):
return plat.iplot_lattice(self.data, **kwargs)
[docs]
@_sub_doc(plat.write_poscar)
@_sig_kwargs(plat.write_poscar, ("poscar_data",))
def write(self, outfile=None, **kwargs):
return plat.write_poscar(self.data, outfile=outfile, **kwargs)
[docs]
@_sub_doc(plat.join_poscars)
@_sig_kwargs(plat.join_poscars, ("poscar_data", "other"))
def join(self, other, direction="c", **kwargs):
return self.__class__(
data=plat.join_poscars(
poscar_data=self.data, other=other.data, direction=direction, **kwargs
)
)
[docs]
@_sub_doc(plat.scale_poscar)
@_sig_kwargs(plat.scale_poscar, ("poscar_data",))
def scale(self, scale=(1, 1, 1), **kwargs):
return self.__class__(data=plat.scale_poscar(self.data, scale, **kwargs))
[docs]
@_sub_doc(plat.set_boundary)
@_sig_kwargs(plat.set_boundary,("poscar_data",))
def set_boundary(self, a = [0,1], b=[0,1],c=[0,1]):
return self.__class__(data = plat.set_boundary(self.data, a=a,b=b,c=c))
[docs]
@_sub_doc(plat.filter_atoms)
@_sig_kwargs(plat.filter_atoms,("poscar_data",))
def filter_atoms(self, func, tol=0.01):
return self.__class__(data = plat.filter_atoms(self.data, func,tol=tol))
[docs]
@_sub_doc(plat.rotate_poscar)
def rotate(self, angle_deg, axis_vec):
return self.__class__(
data=plat.rotate_poscar(self.data, angle_deg=angle_deg, axis_vec=axis_vec)
)
[docs]
@_sub_doc(plat.set_zdir)
def set_zdir(self, hkl, phi=0):
return self.__class__(data=plat.set_zdir(self.data, hkl, phi=phi))
[docs]
@_sub_doc(plat.translate_poscar)
def translate(self, offset):
return self.__class__(data=plat.translate_poscar(self.data, offset=offset))
[docs]
@_sub_doc(plat.repeat_poscar)
def repeat(self, n, direction):
return self.__class__(
data=plat.repeat_poscar(self.data, n=n, direction=direction)
)
[docs]
@_sub_doc(plat.mirror_poscar)
def mirror(self, direction):
return self.__class__(data=plat.mirror_poscar(self.data, direction=direction))
[docs]
@_sub_doc(stk.get_TM, replace={"basis1": "self.basis"})
def get_TM(self, target_basis):
return stk.get_TM(self.data.basis, target_basis)
[docs]
@_sub_doc(plat.transpose_poscar)
def transpose(self, axes=[1, 0, 2]):
return self.__class__(data=plat.transpose_poscar(self.data, axes=axes))
[docs]
@_sub_doc(plat.add_vaccum)
def add_vaccum(self, thickness, direction, left=False):
return self.__class__(
data=plat.add_vaccum(
self.data, thickness=thickness, direction=direction, left=left
)
)
[docs]
@_sub_doc(plat.add_atoms)
def add_atoms(self, name, positions):
return self.__class__(
data=plat.add_atoms(self.data, name=name, positions=positions)
)
[docs]
@_sub_doc(plat.remove_atoms)
def remove_atoms(self, func, fillby=None):
if fillby and not isinstance(fillby, POSCAR):
raise TypeError("fillby should be an instance of POSCAR class.")
return self.__class__(
data=plat.remove_atoms(
self.data, func=func, fillby=fillby.data if fillby else None
)
)
[docs]
@_sub_doc(plat.replace_atoms)
def replace_atoms(self, func, name):
return self.__class__(data=plat.replace_atoms(self.data, func=func, name=name))
[docs]
@_sub_doc(plat.convert_poscar)
def convert(self, atoms_mapping, basis_factor):
return self.__class__(
data=plat.convert_poscar(
self.data, atoms_mapping=atoms_mapping, basis_factor=basis_factor
)
)
[docs]
@_sub_doc(get_kmesh, {"poscar_data :.*\*args :": "*args :"})
@_sig_kwargs(get_kmesh, ("poscar_data",))
def get_kmesh(self, *args, **kwargs):
return get_kmesh(self.data, *args, **kwargs)
[docs]
@_sub_doc(get_kpath, {"rec_basis :.*\n\n": "\n\n"})
@_sig_kwargs(get_kpath, ("rec_basis",))
def get_kpath(self, kpoints, n: int = 5, **kwargs):
return get_kpath(kpoints, n=n, **kwargs, rec_basis=self.data.rec_basis)