Beispiel #1
0
def get_smallest_vectors(supercell_bases,
                         supercell_pos,
                         primitive_pos,
                         symprec=1e-5):
    """Find shortest atomic pair vectors

    Note
    ----
    Shortest vectors from an atom in primitive cell to an atom in
    supercell in the fractional coordinates of primitive cell. If an
    atom in supercell is on the border centered at an atom in
    primitive and there are multiple vectors that have the same
    distance (up to tolerance) and different directions, several
    shortest vectors are stored.
    In fact, this method is not limited to search shortest vectors between
    sueprcell atoms and primitive cell atoms, but can be used to measure
    shortest vectors between atoms in periodic supercell lattice frame.

    Parameters
    ----------
    supercell_bases : ndarray
        Supercell basis vectors as row vectors, (a, b, c)^T. Must be order='C'.
        dtype='double'
        shape=(3, 3)
    supercell_pos : array_like
        Atomic positions in fractional coordinates of supercell.
        dtype='double'
        shape=(size_super, 3)
    primitive_pos : array_like
        Atomic positions in fractional coordinates of supercell. Note that not
        in fractional coodinates of primitive cell.
        dtype='double'
        shape=(size_prim, 3)
    symprec : float, optional, default=1e-5
        Tolerance to find equal distances of vectors

    Returns
    -------
    shortest_vectors : ndarray
        Shortest vectors in supercell coordinates. The 27 in shape is the
        possible maximum number of elements.
        dtype='double'
        shape=(size_super, size_prim, 27, 3)
    multiplicities : ndarray
        Number of equidistance shortest vectors
        dtype='intc'
        shape=(size_super, size_prim)

    """

    reduced_cell_method = 'niggli'
    reduced_bases = get_reduced_bases(supercell_bases,
                                      method=reduced_cell_method,
                                      tolerance=symprec)
    trans_mat_float = np.dot(supercell_bases, np.linalg.inv(reduced_bases))
    trans_mat = np.rint(trans_mat_float).astype(int)
    assert (np.abs(trans_mat_float - trans_mat) < 1e-8).all()
    trans_mat_inv_float = np.linalg.inv(trans_mat)
    trans_mat_inv = np.rint(trans_mat_inv_float).astype(int)
    assert (np.abs(trans_mat_inv_float - trans_mat_inv) < 1e-8).all()

    # Reduce all positions into the cell formed by the reduced bases.
    supercell_fracs = np.dot(supercell_pos, trans_mat)
    supercell_fracs -= np.rint(supercell_fracs)
    supercell_fracs = np.array(supercell_fracs, dtype='double', order='C')
    primitive_fracs = np.dot(primitive_pos, trans_mat)
    primitive_fracs -= np.rint(primitive_fracs)
    primitive_fracs = np.array(primitive_fracs, dtype='double', order='C')

    # For each vector, we will need to consider all nearby images in the
    # reduced bases. The lattice points at which supercell images are searched
    # are composed by linear combinations of three vectors in
    # (0, a, b, c, -a-b-c, -a, -b, -c, a+b+c). There are finally 65 lattice
    # points. There is no proof that this is enough.
    lattice_1D = (-1, 0, 1)
    lattice_4D = np.array([[i, j, k, l] for i in lattice_1D for j in lattice_1D
                           for k in lattice_1D for l in lattice_1D],
                          dtype='intc',
                          order='C')
    bases = [[1, 0, 0], [0, 1, 0], [0, 0, 1], [-1, -1, -1]]
    lattice_points = np.dot(lattice_4D, bases)
    if StrictVersion(np.__version__) >= StrictVersion('1.13.0'):
        lattice_points = np.array(np.unique(lattice_points, axis=0),
                                  dtype='intc',
                                  order='C')
    else:
        unique_indices = np.unique([
            np.nonzero(np.abs(lattice_points - point).sum(axis=1) == 0)[0][0]
            for point in lattice_points
        ])
        lattice_points = np.array(lattice_points[unique_indices],
                                  dtype='intc',
                                  order='C')

    # This shortest_vectors is already used at many locations.
    # Therefore the constant number 27 = 3*3*3 can not be easily changed.
    shortest_vectors = np.zeros(
        (len(supercell_fracs), len(primitive_fracs), 27, 3),
        dtype='double',
        order='C')
    multiplicity = np.zeros((len(supercell_fracs), len(primitive_fracs)),
                            dtype='intc',
                            order='C')
    import phonopy._phonopy as phonoc
    phonoc.gsv_set_smallest_vectors(
        shortest_vectors, multiplicity, supercell_fracs, primitive_fracs,
        lattice_points, np.array(reduced_bases.T, dtype='double', order='C'),
        np.array(trans_mat_inv.T, dtype='intc', order='C'), symprec)

    # Here's where things get interesting.
    # We want to avoid manually iterating over all possible pairings of
    # supercell atoms and primitive atoms, because doing so creates a
    # tight loop in larger structures that is difficult to optimize.
    #
    # Furthermore, it seems wise to call numpy.dot on as large of an array
    # as possible, since numpy can shell out to BLAS to handle the
    # real heavy lifting.

    # lattice_1D = (-1, 0, 1)
    # lattice_points = np.array([[i, j, k]
    #                            for i in lattice_1D
    #                            for j in lattice_1D
    #                            for k in lattice_1D],
    #                           dtype='intc', order='C')

    # # For every atom in the supercell and every atom in the primitive cell,
    # # we want 27 images of the vector between them.
    # #
    # # 'None' is used to insert trivial axes to make these arrays broadcast.
    # #
    # # shape: (size_super, size_prim, 27, 3)
    # candidate_fracs = (
    #     supercell_fracs[:, None, None, :]    # shape: (size_super, 1, 1, 3)
    #     - primitive_fracs[None, :, None, :]  # shape: (1, size_prim, 1, 3)
    #     + lattice_points[None, None, :, :]   # shape: (1, 1, 27, 3)
    # )

    # # To compute the lengths, we want cartesian positions.
    # #
    # # Conveniently, calling 'numpy.dot' between a 4D array and a 2D array
    # # does vector-matrix multiplication on each row vector in the last axis
    # # of the 4D array.
    # #
    # # shape: (size_super, size_prim, 27)
    # lengths = np.array(np.sqrt(
    #     np.sum(np.dot(candidate_fracs, reduced_bases)**2, axis=-1)),
    #                    dtype='double', order='C')

    # # Create the output, initially consisting of all candidate vectors scaled
    # # by the primitive cell.
    # #
    # # shape: (size_super, size_prim, 27, 3)
    # candidate_vectors = np.array(np.dot(candidate_fracs, trans_mat_inv),
    #                              dtype='double', order='C')

    # # The last final bits are done in C.
    # #
    # # We will gather the shortest ones from each list of 27 vectors.
    # shortest_vectors = np.zeros_like(candidate_vectors,
    #                                  dtype='double', order='C')
    # multiplicity = np.zeros(shortest_vectors.shape[:2], dtype='intc',
    #                         order='C')

    # import phonopy._phonopy as phonoc
    # phonoc.gsv_copy_smallest_vectors(shortest_vectors,
    #                                  multiplicity,
    #                                  candidate_vectors,
    #                                  lengths,
    #                                  symprec)

    return shortest_vectors, multiplicity
Beispiel #2
0
def get_smallest_vectors(supercell_bases,
                         supercell_pos,
                         primitive_pos,
                         symprec=1e-5):
    """Find shortest atomic pair vectors

    Note
    ----
    Shortest vectors from an atom in primitive cell to an atom in
    supercell in the fractional coordinates of primitive cell. If an
    atom in supercell is on the border centered at an atom in
    primitive and there are multiple vectors that have the same
    distance (up to tolerance) and different directions, several
    shortest vectors are stored.
    In fact, this method is not limited to search shortest vectors between
    sueprcell atoms and primitive cell atoms, but can be used to measure
    shortest vectors between atoms in periodic supercell lattice frame.

    Parameters
    ----------
    supercell_bases : ndarray
        Supercell basis vectors as row vectors, (a, b, c)^T. Must be order='C'.
        dtype='double'
        shape=(3, 3)
    supercell_pos : array_like
        Atomic positions in fractional coordinates of supercell.
        dtype='double'
        shape=(size_super, 3)
    primitive_pos : array_like
        Atomic positions in fractional coordinates of supercell. Note that not
        in fractional coodinates of primitive cell.
        dtype='double'
        shape=(size_prim, 3)
    symprec : float, optional, default=1e-5
        Tolerance to find equal distances of vectors

    Returns
    -------
    shortest_vectors : ndarray
        Shortest vectors in supercell coordinates. The 27 in shape is the
        possible maximum number of elements.
        dtype='double'
        shape=(size_super, size_prim, 27, 3)
    multiplicities : ndarray
        Number of equidistance shortest vectors
        dtype='intc'
        shape=(size_super, size_prim)

    """

    reduced_bases = get_reduced_bases(supercell_bases,
                                      method='delaunay',
                                      tolerance=symprec)
    trans_mat_float = np.dot(supercell_bases, np.linalg.inv(reduced_bases))
    trans_mat = np.rint(trans_mat_float).astype(int)
    assert (np.abs(trans_mat_float - trans_mat) < 1e-8).all()
    trans_mat_inv_float = np.linalg.inv(trans_mat)
    trans_mat_inv = np.rint(trans_mat_inv_float).astype(int)
    assert (np.abs(trans_mat_inv_float - trans_mat_inv) < 1e-8).all()

    # Reduce all positions into the cell formed by the reduced bases.
    supercell_fracs = np.dot(supercell_pos, trans_mat)
    supercell_fracs -= np.rint(supercell_fracs)
    supercell_fracs = np.array(supercell_fracs, dtype='double', order='C')
    primitive_fracs = np.dot(primitive_pos, trans_mat)
    primitive_fracs -= np.rint(primitive_fracs)
    primitive_fracs = np.array(primitive_fracs, dtype='double', order='C')

    # For each vector, we will need to consider all nearby images in the
    # reduced bases.
    lattice_points = np.array([[i, j, k]
                               for i in (-1, 0, 1)
                               for j in (-1, 0, 1)
                               for k in (-1, 0, 1)],
                              dtype='intc', order='C')

    # Here's where things get interesting.
    # We want to avoid manually iterating over all possible pairings of
    # supercell atoms and primitive atoms, because doing so creates a
    # tight loop in larger structures that is difficult to optimize.
    #
    # Furthermore, it seems wise to call numpy.dot on as large of an array
    # as possible, since numpy can shell out to BLAS to handle the
    # real heavy lifting.

    shortest_vectors = np.zeros(
        (len(supercell_fracs), len(primitive_fracs), 27, 3),
        dtype='double', order='C')
    multiplicity = np.zeros((len(supercell_fracs), len(primitive_fracs)),
                            dtype='intc', order='C')
    import phonopy._phonopy as phonoc
    phonoc.gsv_set_smallest_vectors(
        shortest_vectors,
        multiplicity,
        supercell_fracs,
        primitive_fracs,
        lattice_points,
        np.array(reduced_bases.T, dtype='double', order='C'),
        np.array(trans_mat_inv.T, dtype='intc', order='C'),
        symprec)

    # # For every atom in the supercell and every atom in the primitive cell,
    # # we want 27 images of the vector between them.
    # #
    # # 'None' is used to insert trivial axes to make these arrays broadcast.
    # #
    # # shape: (size_super, size_prim, 27, 3)
    # candidate_fracs = (
    #     supercell_fracs[:, None, None, :]    # shape: (size_super, 1, 1, 3)
    #     - primitive_fracs[None, :, None, :]  # shape: (1, size_prim, 1, 3)
    #     + lattice_points[None, None, :, :]   # shape: (1, 1, 27, 3)
    # )

    # # To compute the lengths, we want cartesian positions.
    # #
    # # Conveniently, calling 'numpy.dot' between a 4D array and a 2D array
    # # does vector-matrix multiplication on each row vector in the last axis
    # # of the 4D array.
    # #
    # # shape: (size_super, size_prim, 27)
    # lengths = np.array(np.sqrt(
    #     np.sum(np.dot(candidate_fracs, reduced_bases)**2, axis=-1)),
    #                    dtype='double', order='C')

    # # Create the output, initially consisting of all candidate vectors scaled
    # # by the primitive cell.
    # #
    # # shape: (size_super, size_prim, 27, 3)
    # candidate_vectors = np.array(np.dot(candidate_fracs, trans_mat_inv),
    #                              dtype='double', order='C')

    # # The last final bits are done in C.
    # #
    # # We will gather the shortest ones from each list of 27 vectors.
    # shortest_vectors = np.zeros_like(candidate_vectors,
    #                                  dtype='double', order='C')
    # multiplicity = np.zeros(shortest_vectors.shape[:2], dtype='intc',
    #                         order='C')

    # import phonopy._phonopy as phonoc
    # phonoc.gsv_copy_smallest_vectors(shortest_vectors,
    #                                  multiplicity,
    #                                  candidate_vectors,
    #                                  lengths,
    #                                  symprec)

    return shortest_vectors, multiplicity