示例#1
0
    def toSphere(self):
        """ Create a sphere which is surely encompassing the *full* shape """
        from .ellipsoid import Sphere

        # Retrieve spheres
        A = self.A.toSphere()
        Ar = A.radius
        Ac = A.center
        B = self.B.toSphere()
        Br = B.radius
        Bc = B.center

        # Calculate the distance between the spheres
        dist = fnorm(Ac - Bc)

        # If one is fully enclosed in the other, we can simply neglect the other
        if dist + Ar <= Br:
            # A is fully enclosed in B (or they are the same)
            return A

        elif dist + Br <= Ar:
            # B is fully enclosed in A (or they are the same)
            return B

        elif dist <= (Ar + Br):
            # We can reduce the sphere drastically because only the overlapping region is important
            # i_r defines the intersection radius, search for Sphere-Sphere Intersection
            dx = (dist**2 - Br**2 + Ar**2) / (2 * dist)

            if dx > dist:
                # the intersection is placed after the radius of B
                # And in this case B is smaller (otherwise dx < 0)
                return B
            elif dx < 0:
                return A

            i_r = msqrt(4 * (dist * Ar)**2 -
                        (dist**2 - Br**2 + Ar**2)**2) / (2 * dist)

            # Now we simply need to find the dx point along the vector Bc - Ac
            # Then we can easily calculate the point from A
            center = Bc - Ac
            center = Ac + center / fnorm(center) * dx
            A = i_r
            B = i_r

        else:
            # In this case there is actually no overlap. So perhaps we should
            # create an infinitisemal sphere such that no point will ever be found
            # Or we should return a new Shape which *always* return False for indices etc.
            center = (Ac + Bc) * 0.5
            # Currently we simply use a very small sphere and put it in the middle between
            # the spheres
            # This should at least speed up comparisons
            A = 0.001
            B = 0.001

        return Sphere(max(A, B), center)
示例#2
0
    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.
        """
        # flatte => copy
        vn = np.asarray(v, dtype=np.float64).flatten()
        vn /= fnorm(vn)
        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)
示例#3
0
def test_sphere_and():
    # For all intersections after
    v = np.array([1.] * 3)
    v /= fnorm(v)
    D = np.linspace(0, 5, 50)
    inside = np.ones(len(D), dtype=bool)

    A = Sphere(2.)
    is_first = True
    inside[:] = True
    for i, d in enumerate(D):
        B = Sphere(1., center=v * d)
        C = (A & B).toSphere()
        if is_first and C.radius < B.radius:
            is_first = False
            inside[i:] = False
        if inside[i]:
            assert C.radius == pytest.approx(B.radius)
        else:
            assert C.radius < B.radius

    A = Sphere(0.5)
    is_first = True
    inside[:] = True
    for i, d in enumerate(D):
        B = Sphere(1., center=v * d)
        C = (A & B).toSphere()
        str(A) + str(B) + str(C)
        if is_first and C.radius < A.radius:
            inside[i:] = False
            is_first = False
        if inside[i]:
            assert C.radius == pytest.approx(A.radius)
        else:
            assert C.radius < A.radius
示例#4
0
    def find_all_bonds(geometry, tol=0.2):
        """
        Finds all bonds present in a geometry.

        Parameters
        -----------
        geometry: sisl.Geometry
            the structure where the bonds should be found.
        tol: float
            the fraction that the distance between atoms is allowed to differ from
            the "standard" in order to be considered a bond.

        Return
        ---------
        np.ndarray of shape (nbonds, 2)
            each item of the array contains the 2 indices of the atoms that participate in the
            bond.
        """
        pt = PeriodicTable()

        bonds = []
        for at in geometry:
            neighs = geometry.close(at, R=[0.1, 3])[-1]

            for neigh in neighs[neighs > at]:
                summed_radius = pt.radius([
                    abs(geometry.atoms[at].Z),
                    abs(geometry.atoms[neigh % geometry.na].Z)
                ]).sum()
                bond_thresh = (1 + tol) * summed_radius
                if bond_thresh > fnorm(geometry[neigh] - geometry[at]):
                    bonds.append([at, neigh])

        return np.array(bonds, dtype=int)
示例#5
0
    def toSphere(self):
        """ Create a sphere which is surely encompassing the *full* shape """
        from .ellipsoid import Sphere

        # Retrieve spheres
        A = self.A.toSphere()
        Ar = A.radius
        Ac = A.center
        B = self.B.toSphere()
        Br = B.radius
        Bc = B.center

        center = (Ac + Bc) * 0.5
        A = Ar + fnorm(center - Ac)
        B = Br + fnorm(center - Bc)

        return Sphere(max(A, B), center)
示例#6
0
    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 = _a.asarrayi(axis).ravel()
        # Convert to unit-vector cell
        for i in axis:
            a = self.cell[i, :] / fnorm(self.cell[i, :])
            b = other.cell[i, :] / fnorm(other.cell[i, :])
            if abs(dot3(a, b) - 1) > 0.001:
                return False
        return True
示例#7
0
    def _projected_1Dcoords(cls, geometry, xyz=None, axis="x", nsc=(1, 1, 1)):
        """
        Moves the 3D positions of the atoms to a 2D supspace.

        In this way, we can plot the structure from the "point of view" that we want.

        NOTE: If axis is one of {"a", "b", "c", "1", "2", "3"} the function doesn't
        project the coordinates in the direction of the lattice vector. The fractional
        coordinates, taking in consideration the three lattice vectors, are returned
        instead.

        Parameters
        ------------
        geometry: sisl.Geometry
            the geometry for which you want the projected coords
        xyz: array-like of shape (natoms, 3), optional
            the 3D coordinates that we want to project.
            otherwise they are taken from the geometry. 
        axis: {"x", "y", "z", "a", "b", "c", "1", "2", "3"} or array-like of shape 3, optional
            the direction to be displayed along the X axis.
        nsc: array-like of shape (3, ), optional
            only used if `axis` is a lattice vector. It is used to rescale everything to the unit
            cell lattice vectors, otherwise `GeometryPlot` doesn't play well with `GridPlot`.

        Returns
        ----------
        np.ndarray of shape (natoms, )
            the 1D coordinates of the geometry, with all positions projected into the line
            defined by axis.
        """
        if xyz is None:
            xyz = geometry.xyz

        if isinstance(axis, str) and axis in ("a", "b", "c", "0", "1", "2"):
            return cls._projected_2Dcoords(geometry,
                                           xyz,
                                           xaxis=axis,
                                           yaxis="a" if axis == "c" else "c",
                                           nsc=nsc)[..., 0]

        # Get the direction that the axis represents
        axis = cls._direction(axis, geometry.cell)

        return xyz.dot(axis / fnorm(axis)) / fnorm(axis)
示例#8
0
 def move(self, v):
     """ Appends additional space to the object """
     # check which cell vector resembles v the most,
     # use that
     cell = np.copy(self.cell)
     p = np.empty([3], np.float64)
     cl = fnorm(cell)
     for i in range(3):
         p[i] = abs(np.sum(cell[i, :] * v)) / cl[i]
     cell[np.argmax(p), :] += v
     return self.copy(cell)
示例#9
0
 def is_orthogonal(self):
     """ Returns true if the cell vectors are orthogonal """
     # Convert to unit-vector cell
     cell = np.copy(self.cell)
     cl = fnorm(cell)
     cell[0, :] = cell[0, :] / cl[0]
     cell[1, :] = cell[1, :] / cl[1]
     cell[2, :] = cell[2, :] / cl[2]
     i_s = dot3(cell[0, :], cell[1, :]) < 0.001
     i_s = dot3(cell[0, :], cell[2, :]) < 0.001 and i_s
     i_s = dot3(cell[1, :], cell[2, :]) < 0.001 and i_s
     return i_s
示例#10
0
    def _bond_length(geom, bond):
        """
        Returns the length of a bond between two atoms.

        Parameters
        ------------
        geom: Geometry
            the structure where the atoms are
        bond: array-like of two int
            the indices of the atoms that form the bond
        """
        return fnorm(geom[bond[1]] - geom[bond[0]])
示例#11
0
    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, :].copy()
        # normalize to get direction vector
        cell[axis, :] += d * (vacuum / fnorm(d))
        return self.copy(cell)
示例#12
0
    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 = fnorm(self.cell[[i, j], :])
        ang = math.acos(dot3(self.cell[i, :], self.cell[j, :]) / (n[0] * n[1]))
        if rad:
            return ang
        return math.degrees(ang)
示例#13
0
    def parameters(self, rad=False):
        r""" Cell parameters of this cell in 3 lengths and 3 angles

        Notes
        -----
        Since we return the length and angles between vectors it may not be possible to
        recreate the same cell. Only in the case where the first lattice vector *only*
        has a Cartesian :math:`x` component will this be the case

        Parameters
        ----------
        rad : bool, optional
           whether the angles are returned in radians (otherwise in degree)

        Returns
        -------
        float
            length of first lattice vector
        float
            length of second lattice vector
        float
            length of third lattice vector
        float
            angle between b and c vectors
        float
            angle between a and c vectors
        float
            angle between a and b vectors
        """
        if rad:
            f = 1.
        else:
            f = 180 / np.pi

        # Calculate length of each lattice vector
        cell = self.cell.copy()
        abc = fnorm(cell)

        from math import acos
        cell = cell / abc.reshape(-1, 1)
        alpha = acos(dot3(cell[1, :], cell[2, :])) * f
        beta = acos(dot3(cell[0, :], cell[2, :])) * f
        gamma = acos(dot3(cell[0, :], cell[1, :])) * f

        return abc[0], abc[1], abc[2], alpha, beta, gamma
示例#14
0
 def radius(self):
     """ Return the radius of the Ellipsoid """
     return fnorm(self._v)
示例#15
0
    def param_circle(self, sc, N_or_dk, kR, normal, origo, loop=False):
        r""" Create a parameterized k-point list where the k-points are generated on a circle around an origo

        The generated circle is a perfect circle in the reciprocal space (Cartesian coordinates).
        To generate a perfect circle in units of the reciprocal lattice vectors one can
        generate the circle for a diagonal supercell with side-length :math:`2\pi`, see
        example below.

        Parameters
        ----------
        sc : SuperCell, or SuperCellChild
           the supercell used to construct the k-points
        N_or_dk : int
           number of k-points generated using the parameterization (if an integer),
           otherwise it specifies the discretization length on the circle (in 1/Ang),
           If the latter case will use less than 4 points a warning will be raised and
           the number of points increased to 4.
        kR : float
           radius of the k-point. In 1/Ang
        normal : array_like of float
           normal vector to determine the circle plane
        origo : array_like of float
           origo of the circle used to generate the circular parameterization
        loop : bool, optional
           whether the first and last point are equal

        Examples
        --------

        >>> sc = SuperCell([1, 1, 10, 90, 90, 60])
        >>> bz = BrillouinZone.param_circle(sc, 10, 0.05, [0, 0, 1], [1./3, 2./3, 0])

        To generate a circular set of k-points in reduced coordinates (reciprocal

        >>> sc = SuperCell([1, 1, 10, 90, 90, 60])
        >>> bz = BrillouinZone.param_circle(sc, 10, 0.05, [0, 0, 1], [1./3, 2./3, 0])
        >>> bz_rec = BrillouinZone.param_circle(2*np.pi, 10, 0.05, [0, 0, 1], [1./3, 2./3, 0])
        >>> bz.k[:, :] = bz_rec.k[:, :]

        Returns
        -------
        BrillouinZone : with the parameterized k-points.
        """
        if isinstance(N_or_dk, Integral):
            N = N_or_dk
        else:
            # Calculate the required number of points
            N = int(kR ** 2 * np.pi / N_or_dk + 0.5)
            if N < 4:
                N = 4
                info('BrillouinZone.param_circle increased the number of circle points to 4.')

        # Conversion object
        bz = BrillouinZone(sc)

        normal = _a.arrayd(normal)
        origo = _a.arrayd(origo)
        k_n = bz.tocartesian(normal)
        k_o = bz.tocartesian(origo)

        # Generate a preset list of k-points on the unit-circle
        if loop:
            radians = _a.aranged(N) / (N-1) * 2 * np.pi
        else:
            radians = _a.aranged(N) / N * 2 * np.pi
        k = _a.emptyd([N, 3])
        k[:, 0] = np.cos(radians)
        k[:, 1] = np.sin(radians)
        k[:, 2] = 0.

        # Now generate the rotation
        _, theta, phi = cart2spher(k_n)
        if theta != 0:
            pv = _a.arrayd([k_n[0], k_n[1], 0])
            pv /= fnorm(pv)
            q = Quaternion(phi, pv, rad=True) * Quaternion(theta, [0, 0, 1], rad=True)
        else:
            q = Quaternion(0., [0, 0, k_n[2] / abs(k_n[2])], rad=True)

        # Calculate k-points
        k = q.rotate(k)
        k *= kR / fnorm(k).reshape(-1, 1)
        k = bz.toreduced(k + k_o)

        # The sum of weights is equal to the BZ area
        W = np.pi * kR ** 2
        w = np.repeat([W / N], N)

        return BrillouinZone(sc, k, w)
示例#16
0
    def initialize(self):
        """ Initialize the internal data-arrays used for efficient calculation of the real-space quantities

        This method should first be called *after* all options has been specified.

        If the user hasn't specified the ``bz`` value as an option this method will update the internal
        integration Brillouin zone based on the ``dk`` option.
        """
        # Try and guess the directions
        unfold = self._unfold
        nsc = self.parent.nsc.copy()
        axes = self._options['axes']
        if axes is None:
            if nsc[2] == 1:
                axes = _a.arrayi([0, 1])
            elif nsc[1] == 1:
                axes = _a.arrayi([0, 2])
            elif nsc[0] == 1:
                axes = _a.arrayi([1, 2])
            else:
                raise ValueError(self.__class__.__name__ + '.initialize currently only supports a 2D real-space self-energy, hence the MonkhorstPack grid should reflect only 2 periodic directions.')

            self._options['axes'] = axes

        # Check that we have periodicity along the chosen axes
        nsc_sum = nsc[axes].sum()
        if nsc_sum == 1:
            raise ValueError(self.__class__.__name__ + '.initialize found no periodic directions for the chosen integration axes: {} and {}.'.format(*nsc[axes]))
        elif nsc_sum < 6:
            raise ValueError((self.__class__.__name__ + '.initialize found one periodic direction '
                              'out of two for the chosen integration axes: {} and {}. '
                              'For 1D systems the regular surface self-energy method is appropriate.').format(*nsc[axes]))

        if self._options['semi_axis'] is None and self._options['k_axis'] is None:
            # None of the axis has been described
            if nsc[axes[0]] > 3:
                k_ax = axes[0]
                s_ax = axes[1]
            elif nsc[axes[1]] > 3:
                k_ax = axes[1]
                s_ax = axes[0]
            else:
                # Choose the direction of k to be the smallest shortest
                sc = self.parent.sc.tile(unfold[axes[0]], axes[0]).tile(unfold[axes[1]], axes[1])
                rcell = fnorm(sc.rcell)[axes]
                k_ax = axes[np.argmax(rcell)]
                if k_ax == axes[0]:
                    s_ax = axes[1]
                else:
                    s_ax = axes[0]
            self._options['semi_axis'] = s_ax
            self._options['k_axis'] = k_ax

            s_ax = 'ABC'[s_ax]
            k_ax = 'ABC'[k_ax]
            info(self.__class__.__name__ + '.initialize determined the semi-inf- and k-directions to be: {} and {}'.format(s_ax, k_ax))

        elif self._options['k_axis'] is None:
            s_ax = self._options['semi_axis']
            if s_ax == axes[0]:
                k_ax = axes[1]
            else:
                k_ax = axes[0]
            self._options['k_axis'] = k_ax

            k_ax = 'ABC'[k_ax]
            info(self.__class__.__name__ + '.initialize determined the k direction to be: {}'.format(k_ax))

        elif self._options['semi_axis'] is None:
            k_ax = self._options['k_axis']
            if k_ax == axes[0]:
                s_ax = axes[1]
            else:
                s_ax = axes[0]
            self._options['semi_axis'] = s_ax

            s_ax = 'ABC'[s_ax]
            info(self.__class__.__name__ + '.initialize determined the semi-infinite direction to be: {}'.format(s_ax))

        k_ax = self._options['k_axis']
        s_ax = self._options['semi_axis']
        if nsc[s_ax] != 3:
            raise ValueError(self.__class__.__name__ + '.initialize found the self-energy direction to be '
                             'incompatible with the parent object. It *must* have 3 supercells along the '
                             'semi-infinite direction.')

        P0 = self.real_space_parent()
        V_atoms = self.real_space_coupling(True)[1]
        self._calc = {
            # The below algorithm requires the direction to be negative
            # if changed, B, C should be reversed below
            'SE': RecursiveSI(self.parent, '-' + 'ABC'[s_ax], eta=self._options['eta']),
            # Used to calculate the real-space self-energy
            'P0': P0.Pk(), # in sparse format
            'S0': P0.Sk(), # in sparse format
            # Orbitals in the coupling atoms
            'orbs': P0.a2o(V_atoms, True).reshape(-1, 1),
        }

        # Update the BrillouinZone integration grid in case it isn't specified
        if self._options['bz'] is None:
            # Update the integration grid
            # Note this integration grid is based on the big system.
            sc = self.parent.sc.tile(unfold[axes[0]], axes[0]).tile(unfold[axes[1]], axes[1])
            rcell = fnorm(sc.rcell)[k_ax]
            nk = [1] * 3
            nk[k_ax] = int(self._options['dk'] * rcell)
            self._options['bz'] = MonkhorstPack(sc, nk, trs=self._options['trs'])
            info(self.__class__.__name__ + '.initialize determined the number of k-points: {}'.format(nk[k_ax]))
示例#17
0
文件: prism4.py 项目: sofiasanz/sisl
 def edge_length(self):
     """ The lengths of each of the vector that defines the cuboid """
     return fnorm(self._v)
示例#18
0
 def length(self):
     """ Length of each lattice vector """
     return fnorm(self.cell)