Source code for sisl.supercell

""" Define a supercell

This class is the basis of many different objects.
"""
from __future__ import print_function, division

import math
import numpy as np
from numbers import Integral

import sisl._array as _a
import sisl.linalg as lin
import sisl._plot as plt
from ._help import ensure_array
from .quaternion import Quaternion

__all__ = ['SuperCell', 'SuperCellChild']


def _cross(u, v):
    """ Cross product u x v """
    return np.array([u[1] * v[2] - u[2] * v[1],
                     u[2] * v[0] - u[0] * v[2],
                     u[0] * v[1] - u[1] * v[0]])


def _dot(u, v):
    """ Dot product u . v """
    return u[0] * v[0] + u[1] * v[1] + u[2] * v[2]


[docs]class SuperCell(object): """ Object to retain a super-cell and its nested values. This supercell object handles cell vectors and its supercell mirrors. """ # We limit the scope of this SuperCell object. __slots__ = ('cell', 'vol', 'nsc', 'n_s', '_sc_off', '_isc_off') def __init__(self, cell, nsc=None): """ Initialize a `SuperCell` object from initial quantities Initialize a `SuperCell` object with cell information and number of supercells in each direction. """ if nsc is None: nsc = [1, 1, 1] # If the length of cell is 6 it must be cell-parameters, not # actual cell coordinates self.cell = self.tocell(cell) # Set the volume self._update_vol() self.nsc = np.ones(3, np.int32) # Set the super-cell self.set_nsc(nsc=nsc) def _update_vol(self): self.vol = np.abs(np.dot(self.cell[0, :], np.cross(self.cell[1, :], self.cell[2, :]) ) ) def _fill(self, non_filled, dtype=None): """ Return a zero filled array of length 3 """ if len(non_filled) == 3: return non_filled # Fill in zeros # This will purposefully raise an exception # if the dimensions of the periodic ones # are not consistent. if dtype is None: try: dtype = non_filled.dtype except Exception: dtype = np.dtype(non_filled[0].__class__) if dtype == np.dtype(int): # Never go higher than int32 for default # guesses on integer lists. dtype = np.int32 f = np.zeros(3, dtype) i = 0 if self.nsc[0] > 1: f[0] = non_filled[i] i += 1 if self.nsc[1] > 1: f[1] = non_filled[i] i += 1 if self.nsc[2] > 1: f[2] = non_filled[i] return f def _fill_sc(self, supercell_index): """ Return a filled supercell index by filling in zeros where needed """ return self._fill(supercell_index, dtype=np.int32)
[docs] def set_nsc(self, nsc=None, a=None, b=None, c=None): """ Sets the number of supercells in the 3 different cell directions nsc: list of int, optional number of supercells in each direction a: integer, optional number of supercells in the first unit-cell vector direction b: integer, optional number of supercells in the second unit-cell vector direction c: integer, optional number of supercells in the third unit-cell vector direction """ if not nsc is None: for i in range(3): if not nsc[i] is None: self.nsc[i] = nsc[i] if a: self.nsc[0] = a if b: self.nsc[1] = b if c: self.nsc[2] = c # Correct for misplaced number of unit-cells for i in range(3): if self.nsc[i] == 0: self.nsc[i] = 1 if np.sum(self.nsc % 2) != 3: raise ValueError( "Supercells has to be of un-even size. The primary cell counts " + "one, all others count 2") # We might use this very often, hence we store it self.n_s = np.prod(self.nsc, dtype=np.int32) self._sc_off = np.zeros([self.n_s, 3], np.int32) self._isc_off = np.zeros(self.nsc, np.int32) n = self.nsc # We define the following ones like this: def ret_range(val): i = val // 2 return range(-i, i+1) x = ret_range(n[0]) y = ret_range(n[1]) z = ret_range(n[2]) i = 0 for iz in z: for iy in y: for ix in x: if ix == 0 and iy == 0 and iz == 0: continue # Increment index i += 1 # The offsets for the supercells in the # sparsity pattern self._sc_off[i, 0] = ix self._sc_off[i, 1] = iy self._sc_off[i, 2] = iz self._update_isc_off()
def _update_isc_off(self): """ Internal routine for updating the supercell indices """ for i in range(self.n_s): d = self.sc_off[i, :] self._isc_off[d[0], d[1], d[2]] = i @property def sc_off(self): """ Integer supercell offsets """ return self._sc_off @sc_off.setter def sc_off(self, sc_off): """ Set the supercell offset """ self._sc_off[:, :] = np.array(sc_off, order='C', dtype=np.int32) self._update_isc_off() @property def isc_off(self): """ Internal indexed supercell ``[ia, ib, ic] == i`` """ return self._isc_off # Aliases set_supercell = set_nsc def __iter__(self): """ Iterate the supercells and the indices of the supercells """ for i, sc in enumerate(self.sc_off): yield i, sc
[docs] def copy(self, cell=None): """ Returns a copy of the object. """ if cell is None: copy = self.__class__(np.copy(self.cell), nsc=np.copy(self.nsc)) else: copy = self.__class__(np.copy(cell), nsc=np.copy(self.nsc)) # Ensure that the correct super-cell information gets carried through if not np.all(copy.sc_off == self.sc_off): copy.sc_off = self.sc_off return copy
[docs] def fit(self, xyz, axis=None, tol=0.05): """ Fit the supercell to `xyz` such that the unit-cell becomes periodic in the specified directions The fitted supercell tries to determine the unit-cell parameters by solving a set of linear equations corresponding to the current supercell vectors. >>> numpy.linalg.solve(self.cell.T, xyz.T) # doctest: +SKIP Parameters ---------- xyz : array_like ``shape(*, 3)`` the coordinates that we will wish to encompass and analyze. axis : None or array_like if ``None`` equivalent to ``[0, 1, 2]``, else only the cell-vectors along the provided axis will be used tol : float tolerance (in Angstrom) of the positions. I.e. we neglect coordinates which are not within the radius of this magnitude """ # In case the passed coordinates are from a Geometry from .geometry import Geometry if isinstance(xyz, Geometry): xyz = xyz.xyz[:, :] cell = np.copy(self.cell[:, :]) # Solve to get the divisions in the current cell x = lin.solve(cell.T, xyz.T).T # Now we should figure out the correct repetitions # by rounding to integer positions of the cell vectors ix = np.rint(x) # Figure out the displacements from integers # Then reduce search space by removing those coordinates # that are more than the tolerance. dist = np.sqrt(np.sum(np.dot(cell.T, (x - ix).T) ** 2, axis=0)) idx = (dist <= tol).nonzero()[0] if len(idx) < 0: raise ValueError(('Could not fit the cell parameters to the coordinates ' 'due to insufficient accuracy (try increase the tolerance)')) # Reduce problem to allowed values below the tolerance x = x[idx, :] ix = ix[idx, :] # Reduce to total repetitions ireps = np.amax(ix, axis=0) - np.amin(ix, axis=0) + 1 # Only repeat the axis requested if isinstance(axis, Integral): axis = [axis] # Reduce the non-set axis if not axis is None: for ax in [0, 1, 2]: if ax not in axis: ireps[ax] = 1 # Enlarge the cell vectors cell[0, :] *= ireps[0] cell[1, :] *= ireps[1] cell[2, :] *= ireps[2] return self.copy(cell)
[docs] def swapaxes(self, a, b): """ Returns `SuperCell` with swapped axis If ``swapaxes(0,1)`` it returns the 0 in the 1 values. """ # Create index vector idx = np.arange(3, dtype=np.int32) idx[b] = a idx[a] = b # There _can_ be errors when sc_off isn't created by sisl return self.__class__(np.copy(self.cell[idx, :], order='C'), nsc=self.nsc[idx])
[docs] def plane(self, ax1, ax2, origo=True): """ Query point and plane-normal for the plane spanning `ax1` and `ax2` Parameters ---------- ax1 : int the first axis vector ax2 : int the second axis vector origo : bool, optional whether the plane intersects the origo or the opposite corner of the unit-cell. Returns ------- n : array_like planes normal vector (pointing outwards with regards to the cell) p : array_like a point on the plane Examples -------- All 6 faces of the supercell can be retrieved like this: >>> n1, p1 = self.plane(0, 1, True) # doctest: +SKIP >>> n2, p2 = self.plane(0, 1, False) # doctest: +SKIP >>> n3, p3 = self.plane(0, 2, True) # doctest: +SKIP >>> n4, p4 = self.plane(0, 2, False) # doctest: +SKIP >>> n5, p5 = self.plane(1, 2, True) # doctest: +SKIP >>> n6, p6 = self.plane(1, 2, False) # doctest: +SKIP However, for performance critical calculations it may be advantageous to do this: >>> uc = self.cell.sum(0) # doctest: +SKIP >>> n1, p1 = self.sc.plane(0, 1) # doctest: +SKIP >>> n2 = -n1 # doctest: +SKIP >>> p2 = p1 + uc # doctest: +SKIP >>> n3, p3 = self.sc.plane(0, 2) # doctest: +SKIP >>> n4 = -n3 # doctest: +SKIP >>> p4 = p3 + uc # doctest: +SKIP >>> n5, p5 = self.sc.plane(1, 2) # doctest: +SKIP >>> n6 = -n5 # doctest: +SKIP >>> p6 = p5 + uc # doctest: +SKIP Secondly, the variables ``p1``, ``p3`` and ``p5`` are always ``[0, 0, 0]`` and ``p2``, ``p4`` and ``p6`` are always ``uc``. Hence this may be used to further reduce certain computations. """ cell = self.cell n = _cross(cell[ax1, :], cell[ax2, :]) # Normalize n /= _dot(n, n) ** .5 # Now we need to figure out if the normal vector # is pointing outwards # Take the cell center up = cell.sum(0) # Calculate the distance from the plane to the center of the cell # If d is positive then the normal vector is pointing towards # the center, so rotate 180 if _dot(n, up / 2) > 0.: n *= -1 if origo: return n, _a.zerosd(3) # We have to reverse the normal vector return -n, up
@property def rcell(self): """ Returns the reciprocal cell for the `SuperCell` without ``2*np.pi`` Note: The returned vectors are still in [0, :] format and not as returned by an inverse LAPACK algorithm. """ # Calculate the reciprocal cell # This should probably be changed and checked for # transposition cell = self.cell rcell = np.empty([3, 3], dtype=cell.dtype) rcell[0, 0] = cell[1, 1] * cell[2, 2] - cell[1, 2] * cell[2, 1] rcell[0, 1] = cell[1, 2] * cell[2, 0] - cell[1, 0] * cell[2, 2] rcell[0, 2] = cell[1, 0] * cell[2, 1] - cell[1, 1] * cell[2, 0] rcell[1, 0] = cell[2, 1] * cell[0, 2] - cell[2, 2] * cell[0, 1] rcell[1, 1] = cell[2, 2] * cell[0, 0] - cell[2, 0] * cell[0, 2] rcell[1, 2] = cell[2, 0] * cell[0, 1] - cell[2, 1] * cell[0, 0] rcell[2, 0] = cell[0, 1] * cell[1, 2] - cell[0, 2] * cell[1, 1] rcell[2, 1] = cell[0, 2] * cell[1, 0] - cell[0, 0] * cell[1, 2] rcell[2, 2] = cell[0, 0] * cell[1, 1] - cell[0, 1] * cell[1, 0] dot = np.dot rcell[0, :] = rcell[0, :] / dot(rcell[0, :], cell[0, :]) rcell[1, :] = rcell[1, :] / dot(rcell[1, :], cell[1, :]) rcell[2, :] = rcell[2, :] / dot(rcell[2, :], cell[2, :]) return rcell * 2. * np.pi
[docs] def rotatea(self, angle, only='abc', rad=False): return self.rotate(angle, self.cell[0, :], only=only, rad=rad)
[docs] def rotateb(self, angle, only='abc', rad=False): return self.rotate(angle, self.cell[1, :], only=only, rad=rad)
[docs] def rotatec(self, angle, only='abc', rad=False): return self.rotate(angle, self.cell[2, :], only=only, rad=rad)
[docs] def rotate(self, angle, v, only='abc', rad=False): """ Rotates the supercell, in-place by the angle around the vector One can control which cell vectors are rotated by designating them individually with ``only='[abc]'``. Parameters ---------- angle : float the angle of which the geometry should be rotated v : array_like [3] the vector around the rotation is going to happen v = [1,0,0] will rotate in the ``yz`` plane rad : bool, optional Whether the angle is in radians (True) or in degrees (False) only : ('abc'), str, optional only rotate the designated cell vectors. """ vn = np.copy(np.asarray(v, dtype=np.float64)[:]) vn /= np.sum(vn ** 2) ** .5 q = Quaternion(angle, vn, rad=rad) q /= q.norm() # normalize the quaternion cell = np.copy(self.cell) if 'a' in only: cell[0, :] = q.rotate(self.cell[0, :]) if 'b' in only: cell[1, :] = q.rotate(self.cell[1, :]) if 'c' in only: cell[2, :] = q.rotate(self.cell[2, :]) return self.copy(cell)
[docs] def offset(self, isc=None): """ Returns the supercell offset of the supercell index """ if isc is None: return np.array([0, 0, 0], np.float64) return np.dot(isc, self.cell)
[docs] def add_vacuum(self, vacuum, axis): """ Add vacuum along the `axis` lattice vector Parameters ---------- vacuum : float amount of vacuum added, in Ang axis : int the lattice vector to add vacuum along """ cell = np.copy(self.cell) d = cell[axis, :] # normalize to get direction vector d = d / np.sum(d ** 2) ** .5 cell[axis, :] += d * vacuum return self.copy(cell)
[docs] def sc_index(self, sc_off): """ Returns the integer index in the sc_off list that corresponds to `sc_off` Returns the integer for the supercell """ def _assert(m, v): if np.all(np.abs(v) > m): raise ValueError("Requesting a non-existing supercell index") hsc = self.nsc // 2 if isinstance(sc_off[0], np.ndarray): _assert(hsc[0], sc_off[:, 0]) _assert(hsc[1], sc_off[:, 1]) _assert(hsc[2], sc_off[:, 2]) return self._isc_off[sc_off[:, 0], sc_off[:, 1], sc_off[:, 2]] elif isinstance(sc_off[0], (tuple, list)): # We are dealing with a list of lists sc_off = np.asarray(sc_off) _assert(hsc[0], sc_off[:, 0]) _assert(hsc[1], sc_off[:, 1]) _assert(hsc[2], sc_off[:, 2]) return self._isc_off[sc_off[:, 0], sc_off[:, 1], sc_off[:, 2]] # Fall back to the other routines sc_off = self._fill_sc(sc_off) if sc_off[0] is not None and sc_off[1] is not None and sc_off[2] is not None: _assert(hsc[0], sc_off[0]) _assert(hsc[1], sc_off[1]) _assert(hsc[2], sc_off[2]) return self._isc_off[sc_off[0], sc_off[1], sc_off[2]] # We build it because there are 'none' idx = [] for i in range(self.n_s): if (sc_off[0] == self.sc_off[i, 0] or sc_off[0] is None) and \ (sc_off[1] == self.sc_off[i, 1] or sc_off[1] is None) and \ (sc_off[2] == self.sc_off[i, 2] or sc_off[2] is None): idx.append(i) return idx
[docs] def scale(self, scale): """ Scale lattice vectors Parameters ---------- scale : ``float`` the scale factor for the new lattice vectors """ return self.copy(self.cell * scale)
[docs] def tile(self, reps, axis): """ Extend the unit-cell `reps` times along the `axis` lattice vector Notes ----- This is *exactly* equivalent to the `repeat` routine. Parameters ---------- reps : int number of times the unit-cell is repeated along the specified lattice vector axis : int the lattice vector along which the repetition is performed """ cell = np.copy(self.cell) nsc = np.copy(self.nsc) cell[axis, :] *= reps # Only reduce the size if it is larger than 5 if nsc[axis] > 3 and reps > 1: nsc[axis] = max(1, nsc[axis] // 2 - (reps - 1)) * 2 + 1 return self.__class__(cell, nsc=nsc)
[docs] def repeat(self, reps, axis): """ Extend the unit-cell `reps` times along the `axis` lattice vector Notes ----- This is *exactly* equivalent to the `tile` routine. Parameters ---------- reps : int number of times the unit-cell is repeated along the specified lattice vector axis : int the lattice vector along which the repetition is performed """ return self.tile(reps, axis)
[docs] def cut(self, seps, axis): """ Cuts the cell into several different sections. """ cell = np.copy(self.cell) cell[axis, :] /= seps return self.copy(cell)
[docs] def append(self, other, axis): """ Appends other `SuperCell` to this grid along axis """ cell = np.copy(self.cell) cell[axis, :] += other.cell[axis, :] return self.copy(cell)
[docs] def prepend(self, other, axis): """ Prepends other `SuperCell` to this grid along axis For a `SuperCell` object this is equivalent to `append`. """ return self.append(other, axis)
[docs] def move(self, v): """ Appends additional space in the SuperCell object """ # check which cell vector resembles v the most, # use that cell = np.copy(self.cell) p = np.empty([3], np.float64) for i in range(3): p[i] = abs(np.sum(cell[i, :] * v)) / np.sum(cell[i, :]**2)**.5 cell[np.argmax(p), :] += v return self.copy(cell)
translate = move
[docs] def center(self, axis=None): """ Returns center of the `SuperCell`, possibly with respect to an axis """ if axis is None: return np.sum(self.cell, axis=0) / 2 return self.cell[axis, :] / 2
@classmethod
[docs] def tocell(cls, *args): """ Returns a 3x3 unit-cell dependent on the input If you supply a single argument it is regarded as either a) a proper unit-cell b) the diagonal elements in the unit-cell If you supply 3 arguments it will be the same as the diagonal elements of the unit-cell If you supply 6 arguments it will be the same as the cell parameters, a, b, c, alpha, beta, gamma. The angles should be provided in degree (not radians). """ # Convert into true array (flattened) args = np.asarray(args, np.float64).flatten() nargs = len(args) # A square-box if nargs == 1: return np.diag([args[0]] * 3) # Diagonal components if nargs == 3: return np.diag(args) # Cell parameters if nargs == 6: cell = np.zeros([3, 3], np.float64) a = args[0] b = args[1] c = args[2] alpha = args[3] beta = args[4] gamma = args[5] cell[0, 0] = a g = gamma * np.pi / 180. cg = np.cos(g) sg = np.sin(g) cell[1, 0] = b * cg cell[1, 1] = b * sg b = beta * np.pi / 180. cb = np.cos(b) sb = np.sin(b) cell[2, 0] = c * cb a = alpha * np.pi / 180. d = (np.cos(a) - cb * cg) / sg cell[2, 1] = c * d cell[2, 2] = c * np.sqrt(sb**2 - d**2) return cell # A complete cell if nargs == 9: args.shape = (3, 3) return np.copy(args) raise ValueError( "Creating a unit-cell has to have 1, 3 or 6 arguments, please correct.")
[docs] def is_orthogonal(self): """ Returns true if the cell vectors are orthogonal """ # Convert to unit-vector cell cell = np.copy(self.cell) cell[0, :] = cell[0, :] / np.sum(cell[0, :]**2) ** .5 cell[1, :] = cell[1, :] / np.sum(cell[1, :]**2) ** .5 cell[2, :] = cell[2, :] / np.sum(cell[2, :]**2) ** .5 i_s = np.dot(cell[0, :], cell[1, :]) < 0.001 i_s = np.dot(cell[0, :], cell[2, :]) < 0.001 and i_s i_s = np.dot(cell[1, :], cell[2, :]) < 0.001 and i_s return i_s
[docs] def parallel(self, other, axis=(0, 1, 2)): """ Returns true if the cell vectors are parallel to `other` Parameters ---------- other : SuperCell the other object to check whether the axis are parallel axis : int or array_like only check the specified axis (default to all) """ axis = ensure_array(axis) # Convert to unit-vector cell for i in axis: a = self.cell[i, :] / np.sum(self.cell[i, :]**2) ** .5 b = other.cell[i, :] / np.sum(other.cell[i, :]**2) ** .5 if abs(np.dot(a, b) - 1) > 0.001: return False return True
[docs] def angle(self, i, j, rad=False): """ The angle between two of the cell vectors Parameters ---------- i : int the first cell vector j : int the second cell vector rad : bool, optional whether the returned value is in radians """ n = np.sum(self.cell[[i, j], :]**2, axis=1) ** .5 ang = math.acos(np.dot(self.cell[i, :], self.cell[j, :]) / (n[0] * n[1])) if rad: return ang return math.degrees(ang)
@staticmethod
[docs] def read(sile, *args, **kwargs): """ Reads SuperCell from the `Sile` using `Sile.read_supercell` Parameters ---------- sile : `Sile` or str a `Sile` object which will be used to read the supercell if it is a string it will create a new sile using `sisl.io.get_sile`. """ # This only works because, they *must* # have been imported previously from sisl.io import get_sile, BaseSile if isinstance(sile, BaseSile): return sile.read_supercell(*args, **kwargs) else: with get_sile(sile) as fh: return fh.read_supercell(*args, **kwargs)
def __repr__(self): """ Returns a string representation of the object """ return self.__class__.__name__ + '{{[{} {} {}]}}'.format(*self.nsc) def __eq__(a, b): """ Equality check """ if not isinstance(b, SuperCell): return False same = np.allclose(a.cell, b.cell) same = same and np.all(a.nsc == b.nsc) return same def __ne__(a, b): """ In-equality check """ return not (a == b) # Create pickling routines def __getstate__(self): """ Returns the state of this object """ return {'cell': self.cell, 'nsc': self.nsc, 'sc_off': self.sc_off} def __setstate__(self, d): """ Re-create the state of this object """ self.__init__(d['cell'], d['nsc']) self.sc_off = d['sc_off'] def __plot__(self, fig_axes=False, axes=None, *args, **kwargs): """ Plot the supercell in a specified ``matplotlib.Axes`` object. Parameters ---------- fig_axes : bool or matplotlib.Axes, optional the figure axes to plot in (if ``matplotlib.Axes`` object). If ``True`` it will create a new figure to plot in. If ``False`` it will try and grap the current figure and the current axes. axes : array_like, optional only plot a subset of the axis, defaults to all axes """ # Default dictionary for passing to newly created figures d = dict() if axes is None: axes = [0, 1, 2] # Ensure we have a new 3D Axes3D if len(axes) == 3: d['projection'] = '3d' if fig_axes is False: try: fig_axes = plt.mlib.gca() except: fig_axes = plt.mlibplt.figure().add_subplot(111, **d) elif fig_axes is True: fig_axes = plt.mlibplt.figure().add_subplot(111, **d) # Create vector objects v = [] for a in axes: v.append(np.vstack(([0.]*len(axes), self.cell[a, axes]))) v = np.array(v) if isinstance(fig_axes, plt.mlib3d.Axes3D): # We should plot in 3D plots for vv in v: fig_axes.plot(vv[:, 0], vv[:, 1], vv[:, 2], *args, **kwargs) v0, v1 = v[0], v[1] fig_axes.plot(v0[1, 0] + v1[:, 0], v0[1, 1] + v1[:, 1], v0[1, 2] + v1[:, 2], *args, **kwargs) plt.mlibplt.zlabel('Ang') raise NotImplementedError('Not implemented for 3D plots') else: for vv in v: fig_axes.plot(vv[:, 0], vv[:, 1], *args, **kwargs) v0, v1 = v[0], v[1] fig_axes.plot(v0[1, 0] + v1[:, 0], v0[1, 1] + v1[:, 1], *args, **kwargs) fig_axes.plot(v1[1, 0] + v0[:, 0], v1[1, 1] + v0[:, 1], *args, **kwargs) plt.mlibplt.xlabel('Ang') plt.mlibplt.ylabel('Ang')
class SuperCellChild(object): """ Class to be inherited by using the ``self.sc`` as a `SuperCell` object Initialize by a `SuperCell` object and get access to several different routines directly related to the `SuperCell` class. """ def set_nsc(self, *args, **kwargs): """ Set the number of super-cells in the `SuperCell` object See `set_nsc` for allowed parameters. See Also -------- SuperCell.set_nsc : the underlying called method """ self.sc.set_nsc(*args, **kwargs) def set_supercell(self, sc): """ Overwrites the local supercell """ if sc is None: # Default supercell is a simple # 1x1x1 unit-cell self.sc = SuperCell([1., 1., 1.]) elif isinstance(sc, SuperCell): self.sc = sc elif isinstance(sc, SuperCellChild): self.sc = sc.sc else: # The supercell is given as a cell self.sc = SuperCell(sc) # Loop over attributes in this class # if it inherits SuperCellChild, we call # set_sc on that too. # Sadly, getattr fails for @property methods # which forces us to use try ... except for a in dir(self): try: if isinstance(getattr(self, a), SuperCellChild): getattr(self, a).set_supercell(self.sc) except: pass set_sc = set_supercell @property def vol(self): """ Returns the inherent `SuperCell` objects `vol` """ return self.sc.vol @property def cell(self): """ Returns the inherent `SuperCell` objects `cell` """ return self.sc.cell @property def rcell(self): """ Returns the inherent `SuperCell` objects `rcell` """ return self.sc.rcell @property def n_s(self): """ Returns the inherent `SuperCell` objects `n_s` """ return self.sc.n_s @property def nsc(self): """ Returns the inherent `SuperCell` objects `nsc` """ return self.sc.nsc @property def sc_off(self): """ Returns the inherent `SuperCell` objects `sc_off` """ return self.sc.sc_off @property def isc_off(self): """ Returns the inherent `SuperCell` objects `isc_off` """ return self.sc.isc_off def add_vacuum(self, vacuum, axis): """ Add vacuum along the `axis` lattice vector Parameters ---------- vacuum : float amount of vacuum added, in Ang axis : int the lattice vector to add vacuum along """ copy = self.copy() copy.set_supercell(self.sc.add_vacuum(vacuum, axis)) return copy def _fill(self, non_filled, dtype=None): return self.sc._fill(non_filled, dtype) def _fill_sc(self, supercell_index): return self.sc._fill_sc(supercell_index) def sc_index(self, *args, **kwargs): """ Call local `SuperCell` object `sc_index` function """ return self.sc.sc_index(*args, **kwargs) def is_orthogonal(self): """ Return true if all cell vectors are linearly independent""" return self.sc.is_orthogonal()