Пример #1
0
    def __init__(
        self,
        lattice_mat=None,
        coords=None,
        elements=None,
        props=None,
        cartesian=False,
        show_props=False,
    ):
        """
        Create atomic structure.

        Requires lattice, coordinates, atom type  information.

        >>> box = [[2.715, 2.715, 0], [0, 2.715, 2.715], [2.715, 0, 2.715]]
        >>> coords = [[0, 0, 0], [0.25, 0.2, 0.25]]
        >>> elements = ["Si", "Si"]
        >>> Si = Atoms(lattice_mat=box, coords=coords, elements=elements)
        >>> print(round(Si.volume,2))
        40.03
        >>> Si.composition
        {'Si': 2}
        >>> round(Si.density,2)
        2.33
        >>> round(Si.packing_fraction,2)
        0.28
        >>> Si.atomic_numbers
        [14, 14]
        >>> Si.num_atoms
        2
        >>> Si.frac_coords[0][0]
        0
        >>> Si.cart_coords[0][0]
        0.0
        >>> coords = [[0, 0, 0], [1.3575 , 1.22175, 1.22175]]
        >>> round(Si.density,2)
        2.33
        >>> Si.spacegroup()
        'C2/m (12)'
        >>> Si.pymatgen_converter()!={}
        True
        """
        self.lattice_mat = np.array(lattice_mat)
        self.show_props = show_props
        self.lattice = Lattice(lattice_mat)
        self.coords = np.array(coords)
        self.elements = elements
        self.cartesian = cartesian
        self.props = props
        if self.props is None:
            self.props = ["" for i in range(len(self.elements))]
        if self.cartesian:
            self.cart_coords = self.coords
            self.frac_coords = np.array(self.lattice.frac_coords(self.coords))
        else:
            self.frac_coords = self.coords
            self.cart_coords = np.array(self.lattice.cart_coords(self.coords))
Пример #2
0
    def make_supercell_matrix(self, scaling_matrix):
        """
        Adapted from Pymatgen.

        Makes a supercell. Allowing to have sites outside the unit cell.

        Args:
            scaling_matrix: A scaling matrix for transforming the lattice
            vectors. Has to be all integers. Several options are possible:
            a. A full 3x3 scaling matrix defining the linear combination
             the old lattice vectors. E.g., [[2,1,0],[0,3,0],[0,0,
             1]] generates a new structure with lattice vectors a' =
             2a + b, b' = 3b, c' = c where a, b, and c are the lattice
             vectors of the original structure.
            b. An sequence of three scaling factors. E.g., [2, 1, 1]
             specifies that the supercell should have dimensions 2a x b x
             c.
            c. A number, which simply scales all lattice vectors by the
             same factor.

        Returns:
            Supercell structure. Note that a Structure is always returned,
            even if the input structure is a subclass of Structure. This is
            to avoid different arguments signatures from causing problems. If
            you prefer a subclass to return its own type, you need to override
            this method in the subclass.
        """
        scale_matrix = np.array(scaling_matrix, np.int16)
        if scale_matrix.shape != (3, 3):
            scale_matrix = np.array(scale_matrix * np.eye(3), np.int16)
        new_lattice = Lattice(np.dot(scale_matrix, self.lattice_mat))

        f_lat = self.lattice_points_in_supercell(scale_matrix)
        c_lat = new_lattice.cart_coords(f_lat)

        new_sites = []
        new_elements = []
        for site, el in zip(self.cart_coords, self.elements):
            for v in c_lat:
                new_elements.append(el)
                tmp = site + v
                new_sites.append(tmp)
        return Atoms(
            lattice_mat=new_lattice.lattice(),
            elements=new_elements,
            coords=new_sites,
            cartesian=True,
        )
Пример #3
0
 def automatic_length_mesh(self, lattice_mat=[], length=20, header="Gamma"):
     """Length based automatic k-points."""
     inv_lat = Lattice(lattice_mat=lattice_mat).inv_lattice()
     b1 = LA.norm(np.array(inv_lat[0]))
     b2 = LA.norm(np.array(inv_lat[1]))
     b3 = LA.norm(np.array(inv_lat[2]))
     n1 = int(max(1, length * b1 + 0.5))
     n2 = int(max(1, length * b2 + 0.5))
     n3 = int(max(1, length * b3 + 0.5))
     return Kpoints3D(kpoints=[[n1, n2, n3]],
                      header=header,
                      kpoint_mode="automatic")
Пример #4
0
 def is_all_acute_or_obtuse(m):
     recp_angles = np.array(Lattice(m).reciprocal_lattice().angles)
     return np.all(recp_angles <= 90) or np.all(recp_angles > 90)
Пример #5
0
    def conventional_standard_structure(self,
                                        tol=1e-5,
                                        international_monoclinic=True):
        """
        Give a conventional cell according to certain conventions.

        The conventionss are defined in Setyawan, W., & Curtarolo,
        S. (2010). High-throughput electronic band structure calculations:
        Challenges and tools. Computational Materials Science,
        49(2), 299-312. doi:10.1016/j.commatsci.2010.05.010
        They basically enforce as much as possible
        norm(a1)<norm(a2)<norm(a3)
        Returns:
            The structure in a conventional standardized cell
        """
        struct = self.refined_atoms
        latt = struct.lattice
        latt_type = self.lattice_system
        sorted_lengths = sorted(latt.abc)
        sorted_dic = sorted(
            [{
                "vec": latt.matrix[i],
                "length": latt.abc[i],
                "orig_index": i
            } for i in [0, 1, 2]],
            key=lambda k: k["length"],
        )

        if latt_type in ("orthorhombic", "cubic"):
            # you want to keep the c axis where it is
            # to keep the C- settings
            transf = np.zeros(shape=(3, 3))
            if self.space_group_symbol.startswith("C"):
                transf[2] = [0, 0, 1]
                a, b = sorted(latt.abc[:2])
                sorted_dic = sorted(
                    [{
                        "vec": latt.matrix[i],
                        "length": latt.abc[i],
                        "orig_index": i
                    } for i in [0, 1]],
                    key=lambda k: k["length"],
                )
                for i in range(2):
                    transf[i][sorted_dic[i]["orig_index"]] = 1
                c = latt.abc[2]
            elif self.space_group_symbol.startswith(
                    "A"
            ):  # change to C-centering to match Setyawan/Curtarolo convention
                transf[2] = [1, 0, 0]
                a, b = sorted(latt.abc[1:])
                sorted_dic = sorted(
                    [{
                        "vec": latt.matrix[i],
                        "length": latt.abc[i],
                        "orig_index": i
                    } for i in [1, 2]],
                    key=lambda k: k["length"],
                )
                for i in range(2):
                    transf[i][sorted_dic[i]["orig_index"]] = 1
                c = latt.abc[0]
            else:
                for i in range(len(sorted_dic)):
                    transf[i][sorted_dic[i]["orig_index"]] = 1
                a, b, c = sorted_lengths
            latt = Lattice.orthorhombic(a, b, c)

        elif latt_type == "tetragonal":
            # find the "a" vectors
            # it is basically the vector repeated two times
            transf = np.zeros(shape=(3, 3))
            a, b, c = sorted_lengths
            for d in range(len(sorted_dic)):
                transf[d][sorted_dic[d]["orig_index"]] = 1

            if abs(b - c) < tol and abs(a - c) > tol:
                a, c = c, a
                transf = np.dot([[0, 0, 1], [0, 1, 0], [1, 0, 0]], transf)
            latt = Lattice.tetragonal(a, c)
        elif latt_type in ("hexagonal", "rhombohedral"):
            # for the conventional cell representation,
            # we allways show the rhombohedral lattices as hexagonal

            # check first if we have the refined structure shows a rhombohedral
            # cell
            # if so, make a supercell
            a, b, c = latt.abc
            if np.all(np.abs([a - b, c - b, a - c]) < 0.001):
                struct.make_supercell(((1, -1, 0), (0, 1, -1), (1, 1, 1)))
                a, b, c = sorted(struct.lattice.abc)

            if abs(b - c) < 0.001:
                a, c = c, a
            new_matrix = [
                [a / 2, -a * np.sqrt(3) / 2, 0],
                [a / 2, a * np.sqrt(3) / 2, 0],
                [0, 0, c],
            ]
            latt = Lattice(new_matrix)
            transf = np.eye(3, 3)

        elif latt_type == "monoclinic":
            # You want to keep the c axis where it is to keep the C- settings

            if self.space_group_symbol.startswith("C"):
                transf = np.zeros(shape=(3, 3))
                transf[2] = [0, 0, 1]
                sorted_dic = sorted(
                    [{
                        "vec": latt.matrix[i],
                        "length": latt.abc[i],
                        "orig_index": i
                    } for i in [0, 1]],
                    key=lambda k: k["length"],
                )
                a = sorted_dic[0]["length"]
                b = sorted_dic[1]["length"]
                c = latt.abc[2]
                new_matrix = None
                for t in itertools.permutations(list(range(2)), 2):
                    m = latt.matrix
                    latt2 = Lattice([m[t[0]], m[t[1]], m[2]])
                    lengths = latt2.abc
                    angles = latt2.angles
                    if angles[0] > 90:
                        # if the angle is > 90 we invert a and b to get
                        # an angle < 90
                        a, b, c, alpha, beta, gamma = Lattice(
                            [-m[t[0]], -m[t[1]], m[2]]).parameters
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = -1
                        transf[1][t[1]] = -1
                        transf[2][2] = 1
                        alpha = np.pi * alpha / 180
                        new_matrix = [
                            [a, 0, 0],
                            [0, b, 0],
                            [0, c * cos(alpha), c * sin(alpha)],
                        ]
                        continue

                    elif angles[0] < 90:
                        transf = np.zeros(shape=(3, 3))
                        # print ('464-470')
                        transf[0][t[0]] = 1
                        transf[1][t[1]] = 1
                        transf[2][2] = 1
                        a, b, c = lengths
                        alpha = np.pi * angles[0] / 180
                        new_matrix = [
                            [a, 0, 0],
                            [0, b, 0],
                            [0, c * cos(alpha), c * sin(alpha)],
                        ]

                if new_matrix is None:
                    # print ('479-482')
                    # this if is to treat the case
                    # where alpha==90 (but we still have a monoclinic sg
                    new_matrix = [[a, 0, 0], [0, b, 0], [0, 0, c]]
                    transf = np.zeros(shape=(3, 3))
                    for c in range(len(sorted_dic)):
                        transf[c][sorted_dic[c]["orig_index"]] = 1
            # if not C-setting
            else:
                # try all permutations of the axis
                # keep the ones with the non-90 angle=alpha
                # and b<c
                new_matrix = None
                for t in itertools.permutations(list(range(3)), 3):
                    m = latt.matrix
                    a, b, c, alpha, beta, gamma = Lattice(
                        [m[t[0]], m[t[1]], m[t[2]]]).parameters
                    if alpha > 90 and b < c:
                        a, b, c, alpha, beta, gamma = Lattice(
                            [-m[t[0]], -m[t[1]], m[t[2]]]).parameters
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = -1
                        transf[1][t[1]] = -1
                        transf[2][t[2]] = 1
                        alpha = np.pi * alpha / 180
                        new_matrix = [
                            [a, 0, 0],
                            [0, b, 0],
                            [0, c * cos(alpha), c * sin(alpha)],
                        ]
                        continue
                    elif alpha < 90 and b < c:
                        # print ('510-515')
                        transf = np.zeros(shape=(3, 3))
                        transf[0][t[0]] = 1
                        transf[1][t[1]] = 1
                        transf[2][t[2]] = 1
                        alpha = np.pi * alpha / 180
                        new_matrix = [
                            [a, 0, 0],
                            [0, b, 0],
                            [0, c * cos(alpha), c * sin(alpha)],
                        ]
                if new_matrix is None:
                    # print ('523-530')
                    # this if is to treat the case
                    # where alpha==90 (but we still have a monoclinic sg
                    new_matrix = [
                        [sorted_lengths[0], 0, 0],
                        [0, sorted_lengths[1], 0],
                        [0, 0, sorted_lengths[2]],
                    ]
                    transf = np.zeros(shape=(3, 3))
                    for c in range(len(sorted_dic)):
                        transf[c][sorted_dic[c]["orig_index"]] = 1

            if international_monoclinic:
                # The above code makes alpha the non-right angle.
                # The following will convert to proper international convention
                # that beta is the non-right angle.
                op = [[0, 1, 0], [1, 0, 0], [0, 0, -1]]
                transf = np.dot(op, transf)
                new_matrix = np.dot(op, new_matrix)
                beta = Lattice(new_matrix).beta
                if beta < 90:
                    op = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]]
                    transf = np.dot(op, transf)
                    new_matrix = np.dot(op, new_matrix)

            latt = Lattice(new_matrix)

        elif latt_type == "triclinic":
            # we use a LLL Minkowski-like reduction for the triclinic cells
            struct = struct.get_lll_reduced_structure()

            a, b, c = latt.abc  # lengths
            alpha, beta, gamma = [np.pi * i / 180 for i in latt.angles]
            new_matrix = None
            test_matrix = [
                [a, 0, 0],
                [b * cos(gamma), b * sin(gamma), 0.0],
                [
                    c * cos(beta),
                    c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma),
                    c * np.sqrt(
                        sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 +
                        2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma),
                ],
            ]

            def is_all_acute_or_obtuse(m):
                recp_angles = np.array(Lattice(m).reciprocal_lattice().angles)
                return np.all(recp_angles <= 90) or np.all(recp_angles > 90)

            if is_all_acute_or_obtuse(test_matrix):
                transf = np.eye(3)
                new_matrix = test_matrix

            test_matrix = [
                [-a, 0, 0],
                [b * cos(gamma), b * sin(gamma), 0.0],
                [
                    -c * cos(beta),
                    -c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma),
                    -c * np.sqrt(
                        sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 +
                        2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma),
                ],
            ]

            if is_all_acute_or_obtuse(test_matrix):
                transf = [[-1, 0, 0], [0, 1, 0], [0, 0, -1]]
                new_matrix = test_matrix

            test_matrix = [
                [-a, 0, 0],
                [-b * cos(gamma), -b * sin(gamma), 0.0],
                [
                    c * cos(beta),
                    c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma),
                    c * np.sqrt(
                        sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 +
                        2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma),
                ],
            ]

            if is_all_acute_or_obtuse(test_matrix):
                transf = [[-1, 0, 0], [0, -1, 0], [0, 0, 1]]
                new_matrix = test_matrix

            test_matrix = [
                [a, 0, 0],
                [-b * cos(gamma), -b * sin(gamma), 0.0],
                [
                    -c * cos(beta),
                    -c * (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma),
                    -c * np.sqrt(
                        sin(gamma)**2 - cos(alpha)**2 - cos(beta)**2 +
                        2 * cos(alpha) * cos(beta) * cos(gamma)) / sin(gamma),
                ],
            ]
            if is_all_acute_or_obtuse(test_matrix):
                transf = [[1, 0, 0], [0, -1, 0], [0, 0, -1]]
                new_matrix = test_matrix

            latt = Lattice(new_matrix)

        new_coords = np.dot(transf, np.transpose(struct.frac_coords)).T
        new_struct = Atoms(
            lattice_mat=latt.matrix,
            elements=struct.elements,
            coords=new_coords,
            cartesian=False,
        )
        return new_struct
Пример #6
0
def make_interface(
    film="",
    subs="",
    atol=1,
    ltol=0.05,
    max_area=500,
    max_area_ratio_tol=1.00,
    seperation=3.0,
    vacuum=8.0,
    apply_strain=False,
):
    """
    Use as main function for making interfaces/heterostructures.

    Return mismatch and other information as info dict.

    Args:
       film: top/film material.

       subs: substrate/bottom/fixed material.

       seperation: minimum seperation between two.

       vacuum: vacuum will be added on both sides.
       So 2*vacuum will be added.
    """
    z = ZSLGenerator(
        max_area_ratio_tol=max_area_ratio_tol,
        max_area=max_area,
        max_length_tol=ltol,
        max_angle_tol=atol,
    )
    film = fix_pbc(film.center_around_origin([0, 0, 0]))
    subs = fix_pbc(subs.center_around_origin([0, 0, 0]))
    matches = list(z(film.lattice_mat[:2], subs.lattice_mat[:2], lowest=True))
    info = {}
    info["mismatch_u"] = "na"
    info["mismatch_v"] = "na"
    info["mismatch_angle"] = "na"
    info["area1"] = "na"
    info["area2"] = "na"
    info["film_sl"] = "na"
    info["matches"] = matches
    info["subs_sl"] = "na"
    uv1 = matches[0]["sub_sl_vecs"]
    uv2 = matches[0]["film_sl_vecs"]
    u = np.array(uv1)
    v = np.array(uv2)
    a1 = u[0]
    a2 = u[1]
    b1 = v[0]
    b2 = v[1]
    mismatch_u = np.linalg.norm(b1) / np.linalg.norm(a1) - 1
    mismatch_v = np.linalg.norm(b2) / np.linalg.norm(a2) - 1
    angle1 = (
        np.arccos(np.dot(a1, a2) / np.linalg.norm(a1) / np.linalg.norm(a2))
        * 180
        / np.pi
    )
    angle2 = (
        np.arccos(np.dot(b1, b2) / np.linalg.norm(b1) / np.linalg.norm(b2))
        * 180
        / np.pi
    )
    mismatch_angle = abs(angle1 - angle2)
    area1 = np.linalg.norm(np.cross(a1, a2))
    area2 = np.linalg.norm(np.cross(b1, b2))
    uv_substrate = uv1
    uv_film = uv2
    substrate_latt = Lattice(
        np.array(
            [uv_substrate[0][:], uv_substrate[1][:], subs.lattice_mat[2, :]]
        )
    )
    _, __, scell = subs.lattice.find_matches(
        substrate_latt, ltol=ltol, atol=atol
    )
    film_latt = Lattice(
        np.array([uv_film[0][:], uv_film[1][:], film.lattice_mat[2, :]])
    )
    scell[2] = np.array([0, 0, 1])
    scell_subs = scell
    _, __, scell = film.lattice.find_matches(film_latt, ltol=ltol, atol=atol)
    scell[2] = np.array([0, 0, 1])
    scell_film = scell
    film_scell = film.make_supercell_matrix(scell_film)
    subs_scell = subs.make_supercell_matrix(scell_subs)
    info["mismatch_u"] = mismatch_u
    info["mismatch_v"] = mismatch_v
    print("mismatch_u,mismatch_v", mismatch_u, mismatch_v)
    info["mismatch_angle"] = mismatch_angle
    info["area1"] = area1
    info["area2"] = area2
    info["film_sl"] = film_scell
    info["subs_sl"] = subs_scell
    substrate_top_z = max(np.array(subs_scell.cart_coords)[:, 2])
    substrate_bot_z = min(np.array(subs_scell.cart_coords)[:, 2])
    film_top_z = max(np.array(film_scell.cart_coords)[:, 2])
    film_bottom_z = min(np.array(film_scell.cart_coords)[:, 2])
    thickness_sub = abs(substrate_top_z - substrate_bot_z)
    thickness_film = abs(film_top_z - film_bottom_z)
    sub_z = (
        (vacuum + substrate_top_z)
        * np.array(subs_scell.lattice_mat[2, :])
        / np.linalg.norm(subs_scell.lattice_mat[2, :])
    )
    shift_normal = (
        sub_z / np.linalg.norm(sub_z) * seperation / np.linalg.norm(sub_z)
    )
    tmp = (
        thickness_film / 2 + seperation + thickness_sub / 2
    ) / np.linalg.norm(subs_scell.lattice_mat[2, :])
    shift_normal = (
        tmp
        * np.array(subs_scell.lattice_mat[2, :])
        / np.linalg.norm(subs_scell.lattice_mat[2, :])
    )
    interface = add_atoms(
        film_scell, subs_scell, shift_normal, apply_strain=apply_strain
    ).center_around_origin([0, 0, 0.5])
    combined = interface.center(vacuum=vacuum).center_around_origin(
        [0, 0, 0.5]
    )
    info["interface"] = combined
    return info
Пример #7
0
class Atoms(object):
    """Generate Atoms python object."""
    def __init__(
        self,
        lattice_mat=None,
        coords=None,
        elements=None,
        props=None,
        cartesian=False,
        show_props=False,
    ):
        """
        Create atomic structure.

        Requires lattice, coordinates, atom type  information.

        >>> box = [[2.715, 2.715, 0], [0, 2.715, 2.715], [2.715, 0, 2.715]]
        >>> coords = [[0, 0, 0], [0.25, 0.2, 0.25]]
        >>> elements = ["Si", "Si"]
        >>> Si = Atoms(lattice_mat=box, coords=coords, elements=elements)
        >>> print(round(Si.volume,2))
        40.03
        >>> Si.composition
        {'Si': 2}
        >>> round(Si.density,2)
        2.33
        >>> round(Si.packing_fraction,2)
        0.28
        >>> Si.atomic_numbers
        [14, 14]
        >>> Si.num_atoms
        2
        >>> Si.frac_coords[0][0]
        0
        >>> Si.cart_coords[0][0]
        0.0
        >>> coords = [[0, 0, 0], [1.3575 , 1.22175, 1.22175]]
        >>> round(Si.density,2)
        2.33
        >>> Si.spacegroup()
        'C2/m (12)'
        >>> Si.pymatgen_converter()!={}
        True
        """
        self.lattice_mat = np.array(lattice_mat)
        self.show_props = show_props
        self.lattice = Lattice(lattice_mat)
        self.coords = np.array(coords)
        self.elements = elements
        self.cartesian = cartesian
        self.props = props
        if self.props is None:
            self.props = ["" for i in range(len(self.elements))]
        if self.cartesian:
            self.cart_coords = self.coords
            self.frac_coords = np.array(self.lattice.frac_coords(self.coords))
        else:
            self.frac_coords = self.coords
            self.cart_coords = np.array(self.lattice.cart_coords(self.coords))

    @property
    def check_polar(self):
        """
        Check if the surface structure is polar.

        Comparing atom types at top and bottom.
        Applicable for sufcae with vaccums only.

        Args:

             file:atoms object (surface with vacuum)

        Returns:
               polar:True/False
        """
        up = 0
        dn = 0
        coords = np.array(self.frac_coords)
        z_max = max(coords[:, 2])
        z_min = min(coords[:, 2])
        for site, element in zip(self.frac_coords, self.elements):
            if site[2] == z_max:
                up = up + Specie(element).Z
            if site[2] == z_min:
                dn = dn + Specie(element).Z
        polar = False
        if up != dn:
            print("Seems like a polar materials.")
            polar = True
        if up == dn:
            print("Non-polar")
            polar = False
        return polar

    def apply_strain(self, strain):
        """Apply a strain(e.g. 0.01, [0,0,.01]) to the lattice."""
        s = (1 + np.array(strain)) * np.eye(3)
        self.lattice_mat = np.dot(self.lattice_mat.T, s).T

    def to_dict(self):
        """Provide dictionary representation of the atoms object."""
        d = OrderedDict()
        d["lattice_mat"] = self.lattice_mat.tolist()
        d["coords"] = np.array(self.coords).tolist()
        d["elements"] = np.array(self.elements).tolist()
        d["abc"] = self.lattice.lat_lengths()
        d["angles"] = self.lattice.lat_angles()
        d["cartesian"] = self.cartesian
        d["props"] = self.props
        return d

    @classmethod
    def from_dict(self, d={}):
        """Form atoms object from the dictionary."""
        return Atoms(
            lattice_mat=d["lattice_mat"],
            elements=d["elements"],
            props=d["props"],
            coords=d["coords"],
            cartesian=d["cartesian"],
        )

    def remove_site_by_index(self, site=0):
        """Remove an atom by its index number."""
        new_els = []
        new_coords = []
        new_props = []
        for ii, i in enumerate(self.frac_coords):
            if ii != site:
                new_els.append(self.elements[ii])
                new_coords.append(self.frac_coords[ii])
                new_props.append(self.props[ii])
        return Atoms(
            lattice_mat=self.lattice_mat,
            elements=new_els,
            coords=np.array(new_coords),
            props=new_props,
            cartesian=False,
        )

    @property
    def get_primitive_atoms(self):
        """Get primitive Atoms using spacegroup information."""
        from jarvis.analysis.structure.spacegroup import Spacegroup3D

        return Spacegroup3D(self).primitive_atoms

    @property
    def raw_distance_matrix(self):
        """Provide distance matrix."""
        coords = np.array(self.cart_coords)
        z = (coords[:, None, :] - coords[None, :, :])**2
        return np.sum(z, axis=-1)**0.5

    def center(self, axis=2, vacuum=18.0, about=None):
        """
        Center structure with vacuum padding.

        Args:
          vacuum:vacuum size

          axis: direction
        """
        cell = self.lattice_mat
        p = self.cart_coords

        dirs = np.zeros_like(cell)
        for i in range(3):
            dirs[i] = np.cross(cell[i - 1], cell[i - 2])
            dirs[i] /= np.sqrt(np.dot(dirs[i], dirs[i]))  # normalize
            if np.dot(dirs[i], cell[i]) < 0.0:
                dirs[i] *= -1

        if isinstance(axis, int):
            axes = (axis, )
        else:
            axes = axis

        # if vacuum and any(self.pbc[x] for x in axes):
        #     warnings.warn(
        #         'You are adding vacuum along a periodic direction!')

        # Now, decide how much each basis vector should be made longer
        longer = np.zeros(3)
        shift = np.zeros(3)
        for i in axes:
            p0 = np.dot(p, dirs[i]).min() if len(p) else 0
            p1 = np.dot(p, dirs[i]).max() if len(p) else 0
            height = np.dot(cell[i], dirs[i])
            if vacuum is not None:
                lng = (p1 - p0 + 2 * vacuum) - height
            else:
                lng = 0.0  # Do not change unit cell size!
            top = lng + height - p1
            shf = 0.5 * (top - p0)
            cosphi = np.dot(cell[i], dirs[i]) / np.sqrt(
                np.dot(cell[i], cell[i]))
            longer[i] = lng / cosphi
            shift[i] = shf / cosphi

        # Now, do it!
        translation = np.zeros(3)
        for i in axes:
            nowlen = np.sqrt(np.dot(cell[i], cell[i]))
            if vacuum is not None or cell[i].any():
                cell[i] = cell[i] * (1 + longer[i] / nowlen)
                translation += shift[i] * cell[i] / nowlen

        new_coords = p + translation
        if about is not None:
            for vector in cell:
                new_coords -= vector / 2.0
            new_coords += about
        atoms = Atoms(
            lattice_mat=cell,
            elements=self.elements,
            coords=new_coords,
            cartesian=True,
        )
        return atoms

    @property
    def volume(self):
        """Get volume of the atoms object."""
        m = self.lattice_mat
        vol = float(abs(np.dot(np.cross(m[0], m[1]), m[2])))
        return vol

    @property
    def composition(self):
        """Get composition of the atoms object."""
        comp = {}
        for i in self.elements:
            comp[i] = comp.setdefault(i, 0) + 1
        return Composition(OrderedDict(comp))

    @property
    def density(self):
        """Get density in g/cm3 of the atoms object."""
        den = float(self.composition.weight * amu_gm) / (float(self.volume) *
                                                         (ang_cm)**3)
        return den

    @property
    def atomic_numbers(self):
        """Get list of atomic numbers of atoms in the atoms object."""
        numbers = []
        for i in self.elements:
            numbers.append(Specie(i).Z)
        return numbers

    @property
    def num_atoms(self):
        """Get number of atoms."""
        return len(self.coords)

    @property
    def uniq_species(self):
        """Get unique elements."""
        return list(set(self.elements))

    def get_center_of_mass(self):
        """Get center of mass of the atoms object."""
        # atomic_mass
        m = []
        for i in self.elements:
            m.append(Specie(i).atomic_mass)
        m = np.array(m)
        com = np.dot(m, self.cart_coords) / m.sum()
        # com = np.linalg.solve(self.lattice_mat.T, com)
        return com

    def get_origin(self):
        """Get center of mass of the atoms object."""
        # atomic_mass
        return self.frac_coords.mean(axis=0)

    def center_around_origin(self, new_origin=[0.0, 0.0, 0.5]):
        """Center around given origin."""
        lat = self.lattice_mat
        typ_sp = self.elements
        natoms = self.num_atoms
        # abc = self.lattice.lat_lengths()
        COM = self.get_origin()
        # COM = self.get_center_of_mass()
        x = np.zeros((natoms))
        y = np.zeros((natoms))
        z = np.zeros((natoms))
        coords = list()
        for i in range(0, natoms):
            # cart_coords[i]=self.cart_coords[i]-COM
            x[i] = self.frac_coords[i][0] - COM[0] + new_origin[0]
            y[i] = self.frac_coords[i][1] - COM[1] + new_origin[1]
            z[i] = self.frac_coords[i][2] - COM[2] + new_origin[2]
            coords.append([x[i], y[i], z[i]])
        struct = Atoms(lattice_mat=lat,
                       elements=typ_sp,
                       coords=coords,
                       cartesian=False)
        return struct

    def pymatgen_converter(self):
        """Get pymatgen representation of the atoms object."""
        try:
            from pymatgen.core.structure import Structure

            return Structure(
                self.lattice_mat,
                self.elements,
                self.frac_coords,
                coords_are_cartesian=False,
            )
        except Exception:
            pass

    def spacegroup(self, symprec=1e-3):
        """Get spacegroup of the atoms object."""
        import spglib

        sg = spglib.get_spacegroup(
            (self.lattice_mat, self.frac_coords, self.atomic_numbers),
            symprec=symprec,
        )
        return sg

    @property
    def packing_fraction(self):
        """Get packing fraction of the atoms object."""
        total_rad = 0
        for i in self.elements:
            total_rad = total_rad + Specie(i).atomic_rad**3
        pf = np.array([4 * np.pi * total_rad / (3 * self.volume)])
        return round(pf[0], 5)

    def lattice_points_in_supercell(self, supercell_matrix):
        """
        Adapted from Pymatgen.

        Returns the list of points on the original lattice contained in the
        supercell in fractional coordinates (with the supercell basis).
        e.g. [[2,0,0],[0,1,0],[0,0,1]] returns [[0,0,0],[0.5,0,0]]

        Args:

            supercell_matrix: 3x3 matrix describing the supercell

        Returns:
            numpy array of the fractional coordinates
        """
        diagonals = np.array([
            [0, 0, 0],
            [0, 0, 1],
            [0, 1, 0],
            [0, 1, 1],
            [1, 0, 0],
            [1, 0, 1],
            [1, 1, 0],
            [1, 1, 1],
        ])
        d_points = np.dot(diagonals, supercell_matrix)

        mins = np.min(d_points, axis=0)
        maxes = np.max(d_points, axis=0) + 1

        ar = (np.arange(mins[0], maxes[0])[:, None] *
              np.array([1, 0, 0])[None, :])
        br = (np.arange(mins[1], maxes[1])[:, None] *
              np.array([0, 1, 0])[None, :])
        cr = (np.arange(mins[2], maxes[2])[:, None] *
              np.array([0, 0, 1])[None, :])

        all_points = ar[:, None, None] + br[None, :, None] + cr[None, None, :]
        all_points = all_points.reshape((-1, 3))

        frac_points = np.dot(all_points, np.linalg.inv(supercell_matrix))

        tvects = frac_points[np.all(frac_points < 1 - 1e-10, axis=1)
                             & np.all(frac_points >= -1e-10, axis=1)]
        assert len(tvects) == round(abs(np.linalg.det(supercell_matrix)))
        return tvects

    def make_supercell_matrix(self, scaling_matrix):
        """
        Adapted from Pymatgen.

        Makes a supercell. Allowing to have sites outside the unit cell.

        Args:
            scaling_matrix: A scaling matrix for transforming the lattice
            vectors. Has to be all integers. Several options are possible:
            a. A full 3x3 scaling matrix defining the linear combination
             the old lattice vectors. E.g., [[2,1,0],[0,3,0],[0,0,
             1]] generates a new structure with lattice vectors a' =
             2a + b, b' = 3b, c' = c where a, b, and c are the lattice
             vectors of the original structure.
            b. An sequence of three scaling factors. E.g., [2, 1, 1]
             specifies that the supercell should have dimensions 2a x b x
             c.
            c. A number, which simply scales all lattice vectors by the
             same factor.

        Returns:
            Supercell structure. Note that a Structure is always returned,
            even if the input structure is a subclass of Structure. This is
            to avoid different arguments signatures from causing problems. If
            you prefer a subclass to return its own type, you need to override
            this method in the subclass.
        """
        scale_matrix = np.array(scaling_matrix, np.int16)
        if scale_matrix.shape != (3, 3):
            scale_matrix = np.array(scale_matrix * np.eye(3), np.int16)
        new_lattice = Lattice(np.dot(scale_matrix, self.lattice_mat))

        f_lat = self.lattice_points_in_supercell(scale_matrix)
        c_lat = new_lattice.cart_coords(f_lat)

        new_sites = []
        new_elements = []
        for site, el in zip(self.cart_coords, self.elements):
            for v in c_lat:
                new_elements.append(el)
                tmp = site + v
                new_sites.append(tmp)
        return Atoms(
            lattice_mat=new_lattice.lattice(),
            elements=new_elements,
            coords=new_sites,
            cartesian=True,
        )

    def make_supercell(self, dim=[2, 2, 2]):
        """Make supercell of dimension dim."""
        dim = np.array(dim)
        if dim.shape == (3, 3):
            dim = np.array([int(np.linalg.norm(v)) for v in dim])
        coords = self.frac_coords
        all_symbs = self.elements  # [i.symbol for i in s.species]
        nat = len(coords)

        new_nat = nat * dim[0] * dim[1] * dim[2]
        new_coords = np.zeros((new_nat, 3))
        new_symbs = []  # np.chararray((new_nat))
        props = []  # self.props

        ct = 0
        for i in range(nat):
            for j in range(dim[0]):
                for k in range(dim[1]):
                    for m in range(dim[2]):
                        props.append(self.props[i])
                        new_coords[ct][0] = (coords[i][0] + j) / float(dim[0])
                        new_coords[ct][1] = (coords[i][1] + k) / float(dim[1])
                        new_coords[ct][2] = (coords[i][2] + m) / float(dim[2])
                        new_symbs.append(all_symbs[i])
                        ct = ct + 1

        nat = new_nat

        nat = len(coords)  # int(s.composition.num_atoms)
        lat = np.zeros((3, 3))
        box = self.lattice_mat
        lat[0][0] = dim[0] * box[0][0]
        lat[0][1] = dim[0] * box[0][1]
        lat[0][2] = dim[0] * box[0][2]
        lat[1][0] = dim[1] * box[1][0]
        lat[1][1] = dim[1] * box[1][1]
        lat[1][2] = dim[1] * box[1][2]
        lat[2][0] = dim[2] * box[2][0]
        lat[2][1] = dim[2] * box[2][1]
        lat[2][2] = dim[2] * box[2][2]
        super_cell = Atoms(
            lattice_mat=lat,
            coords=new_coords,
            elements=new_symbs,
            props=props,
            cartesian=False,
        )
        return super_cell

    def get_lll_reduced_structure(self):
        """Get LLL algorithm based reduced structure."""
        reduced_latt = self.lattice.get_lll_reduced_lattice()
        if reduced_latt != self.lattice:
            return Atoms(
                lattice_mat=reduced_latt._lat,
                elements=self.elements,
                coords=self.frac_coords,
                cartesian=False,
            )
        else:
            return Atoms(
                lattice_mat=self.lattice_mat,
                elements=self.elements,
                coords=self.frac_coords,
                cartesian=False,
            )

    def __repr__(self):
        """Get representation during print statement."""
        return self.get_string()

    def get_string(self, cart=True, sort_order="X"):
        """
        Convert Atoms to string.

        Optional arguments below.

        Args:
          cart:True/False for cartesian/fractional coords.

          sort_order: sort by chemical properties of
                    elements. Default electronegativity.
        """
        system = str(self.composition.formula)
        header = (str(system) + str("\n1.0\n") + str(self.lattice_mat[0][0]) +
                  " " + str(self.lattice_mat[0][1]) + " " +
                  str(self.lattice_mat[0][2]) + "\n" +
                  str(self.lattice_mat[1][0]) + " " +
                  str(self.lattice_mat[1][1]) + " " +
                  str(self.lattice_mat[1][2]) + "\n" +
                  str(self.lattice_mat[2][0]) + " " +
                  str(self.lattice_mat[2][1]) + " " +
                  str(self.lattice_mat[2][2]) + "\n")
        if sort_order is None:
            order = np.argsort(self.elements)
        else:
            order = np.argsort([
                Specie(i).element_property(sort_order) for i in self.elements
            ])
        if cart:
            coords = self.cart_coords
        else:
            coords = self.frac_coords
        coords_ordered = np.array(coords)[order]
        elements_ordered = np.array(self.elements)[order]
        props_ordered = np.array(self.props)[order]
        # check_selective_dynamics = False # TODO
        counts = get_counts(elements_ordered)
        cart_frac = ""
        if cart:
            cart_frac = "\nCartesian\n"
        else:
            cart_frac = "\nDirect\n"

        if "T" in "".join(map(str, self.props[0])):
            middle = (" ".join(map(str, counts.keys())) + "\n" +
                      " ".join(map(str, counts.values())) +
                      "\nSelective dynamics\n" + cart_frac)
        else:
            middle = (" ".join(map(str, counts.keys())) + "\n" +
                      " ".join(map(str, counts.values())) + cart_frac)
        rest = ""

        for ii, i in enumerate(coords_ordered):
            if self.show_props:
                rest = (rest + " ".join(map(str, i)) + " " +
                        str(props_ordered[ii]) + "\n")
            else:
                rest = rest + " ".join(map(str, i)) + "\n"

        result = header + middle + rest
        return result
Пример #8
0
def test_lat():
    box = [[10, 0, 0], [0, 10, 0], [0, 0, 10]]
    lat = Lattice(box)
    td = lat.to_dict()
    fd = Lattice.from_dict(td)
    frac_coords = [[0, 0, 0], [0.5, 0.5, 0.5]]
    cart_coords = [[0, 0, 0], [5, 5, 5]]
    lll = lat._calculate_lll()
    # print ('lll',lll[0][0][0])
    lll_red = lat.get_lll_reduced_lattice()._lat
    # print("lll_educed", lat.get_lll_reduced_lattice()._lat[0][0])
    assert (
        lat.lat_lengths(),
        lat.lat_angles(),
        round(lat.inv_lattice()[0][0], 2),
        [round(i, 2) for i in lat.lat_angles(radians=True)],
        lat.cart_coords(frac_coords)[1][1],
        lat.frac_coords(cart_coords)[1][1],
        lat.volume,
        lat.parameters,
        lll[0][0][0],
        lll_red[0][0],
    ) == (
        [10.0, 10.0, 10.0],
        [90.0, 90.0, 90.0],
        0.1,
        [1.57, 1.57, 1.57],
        5.0,
        0.5,
        1000.0,
        [10.0, 10.0, 10.0, 90.0, 90.0, 90.0],
        10.0,
        10.0,
    )
    d = data('dft_3d')
    for i in d:
        if i['jid'] == 'JVASP-588':
            atoms = Atoms.from_dict(i['atoms'])
            lll = atoms.lattice._calculate_lll()
            assert lll[1][0][0] == -1
Пример #9
0
class Atoms(object):
    """Generate Atoms python object."""

    def __init__(
        self,
        lattice_mat=None,
        coords=None,
        elements=None,
        props=None,
        cartesian=False,
        show_props=False,
    ):
        """
        Create atomic structure.

        Requires lattice, coordinates, atom type  information.

        >>> box = [[2.715, 2.715, 0], [0, 2.715, 2.715], [2.715, 0, 2.715]]
        >>> coords = [[0, 0, 0], [0.25, 0.2, 0.25]]
        >>> elements = ["Si", "Si"]
        >>> Si = Atoms(lattice_mat=box, coords=coords, elements=elements)
        >>> print(round(Si.volume,2))
        40.03
        >>> Si.composition
        {'Si': 2}
        >>> round(Si.density,2)
        2.33
        >>> round(Si.packing_fraction,2)
        0.28
        >>> Si.atomic_numbers
        [14, 14]
        >>> Si.num_atoms
        2
        >>> Si.frac_coords[0][0]
        0
        >>> Si.cart_coords[0][0]
        0.0
        >>> coords = [[0, 0, 0], [1.3575 , 1.22175, 1.22175]]
        >>> round(Si.density,2)
        2.33
        >>> Si.spacegroup()
        'C2/m (12)'
        >>> Si.pymatgen_converter()!={}
        True
        """
        self.lattice_mat = np.array(lattice_mat)
        self.show_props = show_props
        self.lattice = Lattice(lattice_mat)
        self.coords = np.array(coords)
        self.elements = elements
        self.cartesian = cartesian
        self.props = props
        if self.props is None:
            self.props = ["" for i in range(len(self.elements))]
        if self.cartesian:
            self.cart_coords = self.coords
            self.frac_coords = np.array(self.lattice.frac_coords(self.coords))
        else:
            self.frac_coords = self.coords
            self.cart_coords = np.array(self.lattice.cart_coords(self.coords))

    def write_cif(
        self, filename="atoms.cif", comment=None, with_spg_info=True
    ):
        """
        Write CIF format file from Atoms object.

        Caution: can't handle fractional occupancies right now
        """
        if comment is None:
            comment = "CIF file written using JARVIS-Tools package."
        comment = comment + "\n"
        f = open(filename, "w")
        f.write(comment)
        composition = self.composition
        line = "data_" + str(composition.reduced_formula) + "\n"
        f.write(line)
        from jarvis.analysis.structure.spacegroup import Spacegroup3D

        if with_spg_info:
            spg = Spacegroup3D(self)
            line = (
                "_symmetry_space_group_name_H-M  "
                + str("'")
                + str(spg.space_group_symbol)
                + str("'")
                + "\n"
            )
        else:
            line = "_symmetry_space_group_name_H-M  " + str("'P 1'") + "\n"

        f.write(line)
        a, b, c, alpha, beta, gamma = self.lattice.parameters
        f.write("_cell_length_a       %g\n" % a)
        f.write("_cell_length_b       %g\n" % b)
        f.write("_cell_length_c       %g\n" % c)
        f.write("_cell_angle_alpha    %g\n" % alpha)
        f.write("_cell_angle_beta     %g\n" % beta)
        f.write("_cell_angle_gamma    %g\n" % gamma)
        f.write("\n")
        if with_spg_info:
            line = (
                "_symmetry_Int_Tables_number  "
                + str(spg.space_group_number)
                + "\n"
            )
        else:
            line = "_symmetry_Int_Tables_number  " + str(1) + "\n"
        f.write(line)

        line = (
            "_chemical_formula_structural  "
            + str(composition.reduced_formula)
            + "\n"
        )
        f.write(line)
        line = "_chemical_formula_sum  " + str(composition.formula) + "\n"
        f.write(line)
        line = "_cell_volume  " + str(self.volume) + "\n"
        f.write(line)
        reduced, repeat = composition.reduce()
        line = "_cell_formula_units_Z  " + str(repeat) + "\n"
        f.write(line)
        f.write("loop_\n")
        f.write("  _symmetry_equiv_pos_site_id\n")
        f.write(" _symmetry_equiv_pos_as_xyz\n")
        f.write(" 1  'x, y, z'\n")
        f.write("loop_\n")
        f.write(" _atom_site_type_symbol\n")
        f.write(" _atom_site_label\n")
        f.write(" _atom_site_symmetry_multiplicity\n")
        f.write(" _atom_site_fract_x\n")
        f.write(" _atom_site_fract_y\n")
        f.write(" _atom_site_fract_z\n")
        f.write(" _atom_site_fract_occupancy\n")
        order = np.argsort(self.elements)
        coords_ordered = np.array(self.frac_coords)[order]
        elements_ordered = np.array(self.elements)[order]
        occ = 1
        extra = 1
        element_types = []
        # count = 0
        for ii, i in enumerate(elements_ordered):
            if i not in element_types:
                element_types.append(i)
                count = 0
            symbol = i
            count = count + 1
            label = str(i) + str(count)
            element_types.append(i)
            coords = coords_ordered[ii]
            f.write(
                " %s  %s  %s  %7.5f  %7.5f  %7.5f  %s\n"
                % (symbol, label, occ, coords[0], coords[1], coords[2], extra)
            )
        f.close()

    @staticmethod
    def from_cif(filename="atoms.cif"):
        """Read .cif format file."""
        # Warnings:
        # May not work for:
        # system with partial occupancy
        # cif file with multiple blocks
        # _atom_site_U_iso, instead of fractn_x, cartn_x
        # with non-zero _atom_site_attached_hydrogens

        f = open(filename, "r")
        lines = f.read().splitlines()
        f.close()
        lat_a = ""
        lat_b = ""
        lat_c = ""
        lat_alpha = ""
        lat_beta = ""
        lat_gamma = ""
        # TODO: check if chemical_formula_sum
        # matches Atoms.compsotion.reduced_formula

        # chemical_formula_structural = ""
        # chemical_formula_sum = ""
        # chemical_name_mineral = ""
        sym_xyz_line = ""
        for ii, i in enumerate(lines):
            if "_cell_length_a" in i:
                lat_a = float(i.split()[1].split("(")[0])
            if "_cell_length_b" in i:
                lat_b = float(i.split()[1].split("(")[0])
            if "_cell_length_c" in i:
                lat_c = float(i.split()[1].split("(")[0])
            if "_cell_angle_alpha" in i:
                lat_alpha = float(i.split()[1].split("(")[0])
            if "_cell_angle_beta" in i:
                lat_beta = float(i.split()[1].split("(")[0])
            if "_cell_angle_gamma" in i:
                lat_gamma = float(i.split()[1].split("(")[0])
            # if "_chemical_formula_structural" in i:
            #    chemical_formula_structural = i.split()[1]
            # if "_chemical_formula_sum" in i:
            #    chemical_formula_sum = i.split()[1]
            # if "_chemical_name_mineral" in i:
            #    chemical_name_mineral = i.split()[1]
            if "_symmetry_equiv_pos_as_xyz" in i:
                sym_xyz_line = ii
            if "_symmetry_equiv_pos_as_xyz_" in i:
                sym_xyz_line = ii
            if "_symmetry_equiv_pos_as_xyz_" in i:
                sym_xyz_line = ii
            if "_space_group_symop_operation_xyz_" in i:
                sym_xyz_line = ii
            if "_space_group_symop_operation_xyz" in i:
                sym_xyz_line = ii
        symm_ops = []
        terminate = False
        count = 0
        while not terminate:
            print("sym_xyz_line", sym_xyz_line)
            tmp = lines[sym_xyz_line + count + 1]
            if "x" in tmp and "y" in tmp and "z" in tmp:
                # print("tmp", tmp)
                symm_ops.append(tmp)
                count += 1
            else:
                terminate = True
        tmp_arr = [lat_a, lat_b, lat_c, lat_alpha, lat_beta, lat_gamma]
        if any(ele == "" for ele in tmp_arr):
            raise ValueError("Lattice information is incomplete.", tmp_arr)
        lat = Lattice.from_parameters(
            lat_a, lat_b, lat_c, lat_alpha, lat_beta, lat_gamma
        )
        terminate = False
        atom_features = []
        count = 0
        beginning_atom_info_line = 0
        for ii, i in enumerate(lines):
            if "loop_" in i and "_atom_site" in lines[ii + count + 1]:
                beginning_atom_info_line = ii
        while not terminate:
            if "_atom" in lines[beginning_atom_info_line + count + 1]:
                atom_features.append(
                    lines[beginning_atom_info_line + count + 1]
                )
            count += 1
            if "_atom" not in lines[beginning_atom_info_line + count]:
                terminate = True
        terminate = False
        count = 1
        atom_liines = []
        while not terminate:
            number = beginning_atom_info_line + len(atom_features) + count
            if number == len(lines):
                terminate = True
                break
            line = lines[number]
            # print ('tis line',line)
            if len(line.split()) == len(atom_features):
                atom_liines.append(line)
                count += 1
            else:
                terminate = True
        label_index = ""
        fract_x_index = ""
        fract_y_index = ""
        fract_z_index = ""
        cartn_x_index = ""
        cartn_y_index = ""
        cartn_z_index = ""
        occupancy_index = ""
        for ii, i in enumerate(atom_features):
            if "_atom_site_label" in i:
                label_index = ii
            if "fract_x" in i:
                fract_x_index = ii
            if "fract_y" in i:
                fract_y_index = ii
            if "fract_z" in i:
                fract_z_index = ii
            if "cartn_x" in i:
                cartn_x_index = ii
            if "cartn_y" in i:
                cartn_y_index = ii
            if "cartn_z" in i:
                cartn_z_index = ii
            if "occupancy" in i:
                occupancy_index = ii
        if fract_x_index == "" and cartn_x_index == "":
            raise ValueError("Cannot find atomic coordinate info.")
        elements = []
        coords = []
        cif_atoms = None
        if fract_x_index != "":
            for ii, i in enumerate(atom_liines):
                tmp = i.split()
                tmp_lbl = list(
                    Composition.from_string(tmp[label_index]).to_dict().keys()
                )
                elem = tmp_lbl[0]
                coord = [
                    float(tmp[fract_x_index].split("(")[0]),
                    float(tmp[fract_y_index].split("(")[0]),
                    float(tmp[fract_z_index].split("(")[0]),
                ]
                if len(tmp_lbl) > 1:
                    raise ValueError("Check if labesl are correct.", tmp_lbl)
                if (
                    occupancy_index != ""
                    and not float(
                        tmp[occupancy_index].split("(")[0]
                    ).is_integer()
                ):
                    raise ValueError(
                        "Fractional occupancy is not supported.",
                        float(tmp[occupancy_index].split("(")[0]),
                        elem,
                    )

                elements.append(elem)
                coords.append(coord)
            cif_atoms = Atoms(
                lattice_mat=lat.matrix,
                elements=elements,
                coords=coords,
                cartesian=False,
            )
        elif cartn_x_index != "":
            for ii, i in enumerate(atom_liines):
                tmp = i.split()
                tmp_lbl = list(
                    Composition.from_string(tmp[label_index]).to_dict().keys()
                )
                elem = tmp_lbl[0]
                coord = [
                    float(tmp[cartn_x_index].split("(")[0]),
                    float(tmp[cartn_y_index].split("(")[0]),
                    float(tmp[cartn_z_index].split("(")[0]),
                ]
                if len(tmp_lbl) > 1:
                    raise ValueError("Check if labesl are correct.", tmp_lbl)
                if (
                    occupancy_index != ""
                    and not float(
                        tmp[occupancy_index].split("(")[0]
                    ).is_integer()
                ):
                    raise ValueError(
                        "Fractional occupancy is not supported.",
                        float(tmp[occupancy_index].split("(")[0]),
                        elem,
                    )
                elements.append(elem)
                coords.append(coord)
            cif_atoms = Atoms(
                lattice_mat=lat.matrix,
                elements=elements,
                coords=coords,
                cartesian=True,
            )
        else:
            raise ValueError(
                "Cannot find atomic coordinate info from cart or frac."
            )
        # frac_coords=list(cif_atoms.frac_coords)
        cif_elements = cif_atoms.elements
        lat = cif_atoms.lattice.matrix
        if len(symm_ops) > 1:
            frac_coords = list(cif_atoms.frac_coords)
            for i in symm_ops:
                for jj, j in enumerate(frac_coords):
                    new_c_coord = get_new_coord_for_xyz_sym(
                        xyz_string=i, frac_coord=j
                    )
                    new_frac_coord = [new_c_coord][0]
                    if not check_duplicate_coords(frac_coords, new_frac_coord):
                        frac_coords.append(new_frac_coord)
                        cif_elements.append(cif_elements[jj])
            new_atoms = Atoms(
                lattice_mat=lat,
                coords=frac_coords,
                elements=cif_elements,
                cartesian=False,
            )
            cif_atoms = new_atoms
        return cif_atoms

    def write_poscar(self, filename="POSCAR"):
        """Write POSCAR format file from Atoms object."""
        from jarvis.io.vasp.inputs import Poscar

        pos = Poscar(self)
        pos.write_file(filename)

    @property
    def get_xyz_string(self):
        """Get xyz string for atoms."""
        line = str(self.num_atoms) + "\n"
        line += " ".join(map(str, np.array(self.lattice_mat).flatten())) + "\n"
        for i, j in zip(self.elements, self.cart_coords):
            line += (
                str(i)
                + str(" ")
                + str(round(j[0], 4))
                + str(" ")
                + str(round(j[1], 4))
                + str(" ")
                + str(round(j[2], 4))
                + "\n"
            )
        return line

    def write_xyz(self, filename="atoms.xyz"):
        """Write XYZ format file."""
        f = open(filename, "w")
        line = str(self.num_atoms) + "\n"
        f.write(line)
        line = ",".join(map(str, np.array(self.lattice_mat).flatten())) + "\n"
        f.write(line)
        for i, j in zip(self.elements, self.cart_coords):
            f.write("%s %7.5f %7.5f %7.5f\n" % (i, j[0], j[1], j[2]))
        f.close()

    @classmethod
    def from_xyz(self, filename="dsgdb9nsd_057387.xyz", box_size=40):
        """Read XYZ file from to make Atoms object."""
        lattice_mat = [[box_size, 0, 0], [0, box_size, 0], [0, 0, box_size]]
        f = open(filename, "r")
        lines = f.read().splitlines()
        f.close()
        coords = []
        species = []
        natoms = int(lines[0])
        for i in range(natoms):
            tmp = (lines[i + 2]).split()
            coord = [(tmp[1]), (tmp[2]), (tmp[3])]
            coord = [
                0 if "*" in ii else float(ii) for ii in coord
            ]  # dsgdb9nsd_000212.xyz
            coords.append(coord)
            species.append(tmp[0])
        coords = np.array(coords)
        atoms = Atoms(
            lattice_mat=lattice_mat,
            coords=coords,
            elements=species,
            cartesian=True,
        ).center_around_origin(new_origin=[0.5, 0.5, 0.5])
        # print (atoms)
        return atoms

    @classmethod
    def from_poscar(self, filename="POSCAR"):
        """Read POSCAR/CONTCAR file from to make Atoms object."""
        from jarvis.io.vasp.inputs import Poscar

        return Poscar.from_file(filename).atoms

    @property
    def check_polar(self):
        """
        Check if the surface structure is polar.

        Comparing atom types at top and bottom.
        Applicable for sufcae with vaccums only.

        Args:

             file:atoms object (surface with vacuum)

        Returns:
               polar:True/False
        """
        up = 0
        dn = 0
        coords = np.array(self.frac_coords)
        z_max = max(coords[:, 2])
        z_min = min(coords[:, 2])
        for site, element in zip(self.frac_coords, self.elements):
            if site[2] == z_max:
                up = up + Specie(element).Z
            if site[2] == z_min:
                dn = dn + Specie(element).Z
        polar = False
        if up != dn:
            print("Seems like a polar materials.")
            polar = True
        if up == dn:
            print("Non-polar")
            polar = False
        return polar

    def apply_strain(self, strain):
        """Apply a strain(e.g. 0.01, [0,0,.01]) to the lattice."""
        s = (1 + np.array(strain)) * np.eye(3)
        self.lattice_mat = np.dot(self.lattice_mat.T, s).T

    def to_dict(self):
        """Provide dictionary representation of the atoms object."""
        d = OrderedDict()
        d["lattice_mat"] = self.lattice_mat.tolist()
        d["coords"] = np.array(self.coords).tolist()
        d["elements"] = np.array(self.elements).tolist()
        d["abc"] = self.lattice.lat_lengths()
        d["angles"] = self.lattice.lat_angles()
        d["cartesian"] = self.cartesian
        d["props"] = self.props
        return d

    @classmethod
    def from_dict(self, d={}):
        """Form atoms object from the dictionary."""
        return Atoms(
            lattice_mat=d["lattice_mat"],
            elements=d["elements"],
            props=d["props"],
            coords=d["coords"],
            cartesian=d["cartesian"],
        )

    def remove_site_by_index(self, site=0):
        """Remove an atom by its index number."""
        new_els = []
        new_coords = []
        new_props = []
        for ii, i in enumerate(self.frac_coords):
            if ii != site:
                new_els.append(self.elements[ii])
                new_coords.append(self.frac_coords[ii])
                new_props.append(self.props[ii])
        return Atoms(
            lattice_mat=self.lattice_mat,
            elements=new_els,
            coords=np.array(new_coords),
            props=new_props,
            cartesian=False,
        )

    @property
    def get_primitive_atoms(self):
        """Get primitive Atoms using spacegroup information."""
        from jarvis.analysis.structure.spacegroup import Spacegroup3D

        return Spacegroup3D(self).primitive_atoms

    @property
    def raw_distance_matrix(self):
        """Provide distance matrix."""
        coords = np.array(self.cart_coords)
        z = (coords[:, None, :] - coords[None, :, :]) ** 2
        return np.sum(z, axis=-1) ** 0.5

    @property
    def raw_angle_matrix(self, cut_off=5.0):
        """Provide distance matrix."""
        coords = np.array(self.cart_coords)
        angles = []
        for a, b, c in itertools.product(coords, coords, coords):
            if (
                not np.array_equal(a, b)
                and not np.array_equal(b, c)
                and np.linalg.norm((a - b)) < cut_off
                and np.linalg.norm((c - b)) < cut_off
            ):
                angle = get_angle(a, b, c)
                angles.append(angle)
        return angles

    def center(self, axis=2, vacuum=18.0, about=None):
        """
        Center structure with vacuum padding.

        Args:
          vacuum:vacuum size

          axis: direction
        """
        cell = self.lattice_mat
        p = self.cart_coords

        dirs = np.zeros_like(cell)
        for i in range(3):
            dirs[i] = np.cross(cell[i - 1], cell[i - 2])
            dirs[i] /= np.sqrt(np.dot(dirs[i], dirs[i]))  # normalize
            if np.dot(dirs[i], cell[i]) < 0.0:
                dirs[i] *= -1

        if isinstance(axis, int):
            axes = (axis,)
        else:
            axes = axis

        # if vacuum and any(self.pbc[x] for x in axes):
        #     warnings.warn(
        #         'You are adding vacuum along a periodic direction!')

        # Now, decide how much each basis vector should be made longer
        longer = np.zeros(3)
        shift = np.zeros(3)
        for i in axes:
            p0 = np.dot(p, dirs[i]).min() if len(p) else 0
            p1 = np.dot(p, dirs[i]).max() if len(p) else 0
            height = np.dot(cell[i], dirs[i])
            if vacuum is not None:
                lng = (p1 - p0 + 2 * vacuum) - height
            else:
                lng = 0.0  # Do not change unit cell size!
            top = lng + height - p1
            shf = 0.5 * (top - p0)
            cosphi = np.dot(cell[i], dirs[i]) / np.sqrt(
                np.dot(cell[i], cell[i])
            )
            longer[i] = lng / cosphi
            shift[i] = shf / cosphi

        # Now, do it!
        translation = np.zeros(3)
        for i in axes:
            nowlen = np.sqrt(np.dot(cell[i], cell[i]))
            if vacuum is not None or cell[i].any():
                cell[i] = cell[i] * (1 + longer[i] / nowlen)
                translation += shift[i] * cell[i] / nowlen

        new_coords = p + translation
        if about is not None:
            for vector in cell:
                new_coords -= vector / 2.0
            new_coords += about
        atoms = Atoms(
            lattice_mat=cell,
            elements=self.elements,
            coords=new_coords,
            cartesian=True,
        )
        return atoms

    @property
    def volume(self):
        """Get volume of the atoms object."""
        m = self.lattice_mat
        vol = float(abs(np.dot(np.cross(m[0], m[1]), m[2])))
        return vol

    @property
    def composition(self):
        """Get composition of the atoms object."""
        comp = {}
        for i in self.elements:
            comp[i] = comp.setdefault(i, 0) + 1
        return Composition(OrderedDict(comp))

    @property
    def density(self):
        """Get density in g/cm3 of the atoms object."""
        den = float(self.composition.weight * amu_gm) / (
            float(self.volume) * (ang_cm) ** 3
        )
        return den

    @property
    def atomic_numbers(self):
        """Get list of atomic numbers of atoms in the atoms object."""
        numbers = []
        for i in self.elements:
            numbers.append(Specie(i).Z)
        return numbers

    @property
    def num_atoms(self):
        """Get number of atoms."""
        return len(self.coords)

    @property
    def uniq_species(self):
        """Get unique elements."""
        uniq = set()
        return [x for x in self.elements if not (x in uniq or uniq.add(x))]

    def get_center_of_mass(self):
        """Get center of mass of the atoms object."""
        # atomic_mass
        m = []
        for i in self.elements:
            m.append(Specie(i).atomic_mass)
        m = np.array(m)
        com = np.dot(m, self.cart_coords) / m.sum()
        # com = np.linalg.solve(self.lattice_mat.T, com)
        return com

    def get_origin(self):
        """Get center of mass of the atoms object."""
        # atomic_mass
        return self.frac_coords.mean(axis=0)

    def center_around_origin(self, new_origin=[0.0, 0.0, 0.5]):
        """Center around given origin."""
        lat = self.lattice_mat
        typ_sp = self.elements
        natoms = self.num_atoms
        # abc = self.lattice.lat_lengths()
        COM = self.get_origin()
        # COM = self.get_center_of_mass()
        x = np.zeros((natoms))
        y = np.zeros((natoms))
        z = np.zeros((natoms))
        coords = list()
        for i in range(0, natoms):
            # cart_coords[i]=self.cart_coords[i]-COM
            x[i] = self.frac_coords[i][0] - COM[0] + new_origin[0]
            y[i] = self.frac_coords[i][1] - COM[1] + new_origin[1]
            z[i] = self.frac_coords[i][2] - COM[2] + new_origin[2]
            coords.append([x[i], y[i], z[i]])
        struct = Atoms(
            lattice_mat=lat, elements=typ_sp, coords=coords, cartesian=False
        )
        return struct

    def pymatgen_converter(self):
        """Get pymatgen representation of the atoms object."""
        try:
            from pymatgen.core.structure import Structure

            return Structure(
                self.lattice_mat,
                self.elements,
                self.frac_coords,
                coords_are_cartesian=False,
            )
        except Exception:
            print("Requires pymatgen for this functionality.")
            pass

    def phonopy_converter(self, pbc=True):
        """Get phonopy representation of the atoms object."""
        try:
            from phonopy.structure.atoms import Atoms as PhonopyAtoms

            return PhonopyAtoms(
                symbols=self.elements,
                positions=self.cart_coords,
                pbc=pbc,
                cell=self.lattice_mat,
            )
        except Exception:
            print("Requires phonopy for this functionality.")
            pass

    def ase_converter(self, pbc=True):
        """Get ASE representation of the atoms object."""
        try:
            from ase import Atoms as AseAtoms

            return AseAtoms(
                symbols=self.elements,
                positions=self.cart_coords,
                pbc=pbc,
                cell=self.lattice_mat,
            )
        except Exception:
            print("Requires ASE for this functionality.")
            pass

    def spacegroup(self, symprec=1e-3):
        """Get spacegroup of the atoms object."""
        import spglib

        sg = spglib.get_spacegroup(
            (self.lattice_mat, self.frac_coords, self.atomic_numbers),
            symprec=symprec,
        )
        return sg

    @property
    def packing_fraction(self):
        """Get packing fraction of the atoms object."""
        total_rad = 0
        for i in self.elements:
            total_rad = total_rad + Specie(i).atomic_rad ** 3
        pf = np.array([4 * np.pi * total_rad / (3 * self.volume)])
        return round(pf[0], 5)

    def lattice_points_in_supercell(self, supercell_matrix):
        """
        Adapted from Pymatgen.

        Returns the list of points on the original lattice contained in the
        supercell in fractional coordinates (with the supercell basis).
        e.g. [[2,0,0],[0,1,0],[0,0,1]] returns [[0,0,0],[0.5,0,0]]

        Args:

            supercell_matrix: 3x3 matrix describing the supercell

        Returns:
            numpy array of the fractional coordinates
        """
        diagonals = np.array(
            [
                [0, 0, 0],
                [0, 0, 1],
                [0, 1, 0],
                [0, 1, 1],
                [1, 0, 0],
                [1, 0, 1],
                [1, 1, 0],
                [1, 1, 1],
            ]
        )
        d_points = np.dot(diagonals, supercell_matrix)

        mins = np.min(d_points, axis=0)
        maxes = np.max(d_points, axis=0) + 1

        ar = (
            np.arange(mins[0], maxes[0])[:, None]
            * np.array([1, 0, 0])[None, :]
        )
        br = (
            np.arange(mins[1], maxes[1])[:, None]
            * np.array([0, 1, 0])[None, :]
        )
        cr = (
            np.arange(mins[2], maxes[2])[:, None]
            * np.array([0, 0, 1])[None, :]
        )

        all_points = ar[:, None, None] + br[None, :, None] + cr[None, None, :]
        all_points = all_points.reshape((-1, 3))

        frac_points = np.dot(all_points, np.linalg.inv(supercell_matrix))

        tvects = frac_points[
            np.all(frac_points < 1 - 1e-10, axis=1)
            & np.all(frac_points >= -1e-10, axis=1)
        ]
        assert len(tvects) == round(abs(np.linalg.det(supercell_matrix)))
        return tvects

    def make_supercell_matrix(self, scaling_matrix):
        """
        Adapted from Pymatgen.

        Makes a supercell. Allowing to have sites outside the unit cell.

        Args:
            scaling_matrix: A scaling matrix for transforming the lattice
            vectors. Has to be all integers. Several options are possible:
            a. A full 3x3 scaling matrix defining the linear combination
             the old lattice vectors. E.g., [[2,1,0],[0,3,0],[0,0,
             1]] generates a new structure with lattice vectors a' =
             2a + b, b' = 3b, c' = c where a, b, and c are the lattice
             vectors of the original structure.
            b. An sequence of three scaling factors. E.g., [2, 1, 1]
             specifies that the supercell should have dimensions 2a x b x
             c.
            c. A number, which simply scales all lattice vectors by the
             same factor.

        Returns:
            Supercell structure. Note that a Structure is always returned,
            even if the input structure is a subclass of Structure. This is
            to avoid different arguments signatures from causing problems. If
            you prefer a subclass to return its own type, you need to override
            this method in the subclass.
        """
        scale_matrix = np.array(scaling_matrix, np.int16)
        if scale_matrix.shape != (3, 3):
            scale_matrix = np.array(scale_matrix * np.eye(3), np.int16)
        new_lattice = Lattice(np.dot(scale_matrix, self.lattice_mat))

        f_lat = self.lattice_points_in_supercell(scale_matrix)
        c_lat = new_lattice.cart_coords(f_lat)

        new_sites = []
        new_elements = []
        for site, el in zip(self.cart_coords, self.elements):
            for v in c_lat:
                new_elements.append(el)
                tmp = site + v
                new_sites.append(tmp)
        return Atoms(
            lattice_mat=new_lattice.lattice(),
            elements=new_elements,
            coords=new_sites,
            cartesian=True,
        )

    def make_supercell(self, dim=[2, 2, 2]):
        """Make supercell of dimension dim."""
        dim = np.array(dim)
        if dim.shape == (3, 3):
            dim = np.array([int(np.linalg.norm(v)) for v in dim])
        coords = self.frac_coords
        all_symbs = self.elements  # [i.symbol for i in s.species]
        nat = len(coords)

        new_nat = nat * dim[0] * dim[1] * dim[2]
        new_coords = np.zeros((new_nat, 3))
        new_symbs = []  # np.chararray((new_nat))
        props = []  # self.props

        ct = 0
        for i in range(nat):
            for j in range(dim[0]):
                for k in range(dim[1]):
                    for m in range(dim[2]):
                        props.append(self.props[i])
                        new_coords[ct][0] = (coords[i][0] + j) / float(dim[0])
                        new_coords[ct][1] = (coords[i][1] + k) / float(dim[1])
                        new_coords[ct][2] = (coords[i][2] + m) / float(dim[2])
                        new_symbs.append(all_symbs[i])
                        ct = ct + 1

        nat = new_nat

        nat = len(coords)  # int(s.composition.num_atoms)
        lat = np.zeros((3, 3))
        box = self.lattice_mat
        lat[0][0] = dim[0] * box[0][0]
        lat[0][1] = dim[0] * box[0][1]
        lat[0][2] = dim[0] * box[0][2]
        lat[1][0] = dim[1] * box[1][0]
        lat[1][1] = dim[1] * box[1][1]
        lat[1][2] = dim[1] * box[1][2]
        lat[2][0] = dim[2] * box[2][0]
        lat[2][1] = dim[2] * box[2][1]
        lat[2][2] = dim[2] * box[2][2]
        super_cell = Atoms(
            lattice_mat=lat,
            coords=new_coords,
            elements=new_symbs,
            props=props,
            cartesian=False,
        )
        return super_cell

    def get_lll_reduced_structure(self):
        """Get LLL algorithm based reduced structure."""
        reduced_latt = self.lattice.get_lll_reduced_lattice()
        if reduced_latt != self.lattice:
            return Atoms(
                lattice_mat=reduced_latt._lat,
                elements=self.elements,
                coords=self.frac_coords,
                cartesian=False,
            )
        else:
            return Atoms(
                lattice_mat=self.lattice_mat,
                elements=self.elements,
                coords=self.frac_coords,
                cartesian=False,
            )

    def __repr__(self):
        """Get representation during print statement."""
        return self.get_string()

    def get_string(self, cart=True, sort_order="X"):
        """
        Convert Atoms to string.

        Optional arguments below.

        Args:
          cart:True/False for cartesian/fractional coords.

          sort_order: sort by chemical properties of
                    elements. Default electronegativity.
        """
        system = str(self.composition.formula)
        header = (
            str(system)
            + str("\n1.0\n")
            + str(self.lattice_mat[0][0])
            + " "
            + str(self.lattice_mat[0][1])
            + " "
            + str(self.lattice_mat[0][2])
            + "\n"
            + str(self.lattice_mat[1][0])
            + " "
            + str(self.lattice_mat[1][1])
            + " "
            + str(self.lattice_mat[1][2])
            + "\n"
            + str(self.lattice_mat[2][0])
            + " "
            + str(self.lattice_mat[2][1])
            + " "
            + str(self.lattice_mat[2][2])
            + "\n"
        )
        if sort_order is None:
            order = np.argsort(self.elements)
        else:
            order = np.argsort(
                [Specie(i).element_property(sort_order) for i in self.elements]
            )
        if cart:
            coords = self.cart_coords
        else:
            coords = self.frac_coords
        coords_ordered = np.array(coords)[order]
        elements_ordered = np.array(self.elements)[order]
        props_ordered = np.array(self.props)[order]
        # check_selective_dynamics = False # TODO
        counts = get_counts(elements_ordered)
        cart_frac = ""
        if cart:
            cart_frac = "\nCartesian\n"
        else:
            cart_frac = "\nDirect\n"

        if "T" in "".join(map(str, self.props[0])):
            middle = (
                " ".join(map(str, counts.keys()))
                + "\n"
                + " ".join(map(str, counts.values()))
                + "\nSelective dynamics\n"
                + cart_frac
            )
        else:
            middle = (
                " ".join(map(str, counts.keys()))
                + "\n"
                + " ".join(map(str, counts.values()))
                + cart_frac
            )
        rest = ""
        if coords_ordered.ndim == 1:
            coords_ordered = np.array([coords])
        for ii, i in enumerate(coords_ordered):
            if self.show_props:
                rest = (
                    rest
                    + " ".join(map(str, i))
                    + " "
                    + str(props_ordered[ii])
                    + "\n"
                )
            else:
                rest = rest + " ".join(map(str, i)) + "\n"

        result = header + middle + rest
        return result
Пример #10
0
    def from_cif(filename="atoms.cif"):
        """Read .cif format file."""
        # Warnings:
        # May not work for:
        # system with partial occupancy
        # cif file with multiple blocks
        # _atom_site_U_iso, instead of fractn_x, cartn_x
        # with non-zero _atom_site_attached_hydrogens

        f = open(filename, "r")
        lines = f.read().splitlines()
        f.close()
        lat_a = ""
        lat_b = ""
        lat_c = ""
        lat_alpha = ""
        lat_beta = ""
        lat_gamma = ""
        # TODO: check if chemical_formula_sum
        # matches Atoms.compsotion.reduced_formula

        # chemical_formula_structural = ""
        # chemical_formula_sum = ""
        # chemical_name_mineral = ""
        sym_xyz_line = ""
        for ii, i in enumerate(lines):
            if "_cell_length_a" in i:
                lat_a = float(i.split()[1].split("(")[0])
            if "_cell_length_b" in i:
                lat_b = float(i.split()[1].split("(")[0])
            if "_cell_length_c" in i:
                lat_c = float(i.split()[1].split("(")[0])
            if "_cell_angle_alpha" in i:
                lat_alpha = float(i.split()[1].split("(")[0])
            if "_cell_angle_beta" in i:
                lat_beta = float(i.split()[1].split("(")[0])
            if "_cell_angle_gamma" in i:
                lat_gamma = float(i.split()[1].split("(")[0])
            # if "_chemical_formula_structural" in i:
            #    chemical_formula_structural = i.split()[1]
            # if "_chemical_formula_sum" in i:
            #    chemical_formula_sum = i.split()[1]
            # if "_chemical_name_mineral" in i:
            #    chemical_name_mineral = i.split()[1]
            if "_symmetry_equiv_pos_as_xyz" in i:
                sym_xyz_line = ii
            if "_symmetry_equiv_pos_as_xyz_" in i:
                sym_xyz_line = ii
            if "_symmetry_equiv_pos_as_xyz_" in i:
                sym_xyz_line = ii
            if "_space_group_symop_operation_xyz_" in i:
                sym_xyz_line = ii
            if "_space_group_symop_operation_xyz" in i:
                sym_xyz_line = ii
        symm_ops = []
        terminate = False
        count = 0
        while not terminate:
            print("sym_xyz_line", sym_xyz_line)
            tmp = lines[sym_xyz_line + count + 1]
            if "x" in tmp and "y" in tmp and "z" in tmp:
                # print("tmp", tmp)
                symm_ops.append(tmp)
                count += 1
            else:
                terminate = True
        tmp_arr = [lat_a, lat_b, lat_c, lat_alpha, lat_beta, lat_gamma]
        if any(ele == "" for ele in tmp_arr):
            raise ValueError("Lattice information is incomplete.", tmp_arr)
        lat = Lattice.from_parameters(
            lat_a, lat_b, lat_c, lat_alpha, lat_beta, lat_gamma
        )
        terminate = False
        atom_features = []
        count = 0
        beginning_atom_info_line = 0
        for ii, i in enumerate(lines):
            if "loop_" in i and "_atom_site" in lines[ii + count + 1]:
                beginning_atom_info_line = ii
        while not terminate:
            if "_atom" in lines[beginning_atom_info_line + count + 1]:
                atom_features.append(
                    lines[beginning_atom_info_line + count + 1]
                )
            count += 1
            if "_atom" not in lines[beginning_atom_info_line + count]:
                terminate = True
        terminate = False
        count = 1
        atom_liines = []
        while not terminate:
            number = beginning_atom_info_line + len(atom_features) + count
            if number == len(lines):
                terminate = True
                break
            line = lines[number]
            # print ('tis line',line)
            if len(line.split()) == len(atom_features):
                atom_liines.append(line)
                count += 1
            else:
                terminate = True
        label_index = ""
        fract_x_index = ""
        fract_y_index = ""
        fract_z_index = ""
        cartn_x_index = ""
        cartn_y_index = ""
        cartn_z_index = ""
        occupancy_index = ""
        for ii, i in enumerate(atom_features):
            if "_atom_site_label" in i:
                label_index = ii
            if "fract_x" in i:
                fract_x_index = ii
            if "fract_y" in i:
                fract_y_index = ii
            if "fract_z" in i:
                fract_z_index = ii
            if "cartn_x" in i:
                cartn_x_index = ii
            if "cartn_y" in i:
                cartn_y_index = ii
            if "cartn_z" in i:
                cartn_z_index = ii
            if "occupancy" in i:
                occupancy_index = ii
        if fract_x_index == "" and cartn_x_index == "":
            raise ValueError("Cannot find atomic coordinate info.")
        elements = []
        coords = []
        cif_atoms = None
        if fract_x_index != "":
            for ii, i in enumerate(atom_liines):
                tmp = i.split()
                tmp_lbl = list(
                    Composition.from_string(tmp[label_index]).to_dict().keys()
                )
                elem = tmp_lbl[0]
                coord = [
                    float(tmp[fract_x_index].split("(")[0]),
                    float(tmp[fract_y_index].split("(")[0]),
                    float(tmp[fract_z_index].split("(")[0]),
                ]
                if len(tmp_lbl) > 1:
                    raise ValueError("Check if labesl are correct.", tmp_lbl)
                if (
                    occupancy_index != ""
                    and not float(
                        tmp[occupancy_index].split("(")[0]
                    ).is_integer()
                ):
                    raise ValueError(
                        "Fractional occupancy is not supported.",
                        float(tmp[occupancy_index].split("(")[0]),
                        elem,
                    )

                elements.append(elem)
                coords.append(coord)
            cif_atoms = Atoms(
                lattice_mat=lat.matrix,
                elements=elements,
                coords=coords,
                cartesian=False,
            )
        elif cartn_x_index != "":
            for ii, i in enumerate(atom_liines):
                tmp = i.split()
                tmp_lbl = list(
                    Composition.from_string(tmp[label_index]).to_dict().keys()
                )
                elem = tmp_lbl[0]
                coord = [
                    float(tmp[cartn_x_index].split("(")[0]),
                    float(tmp[cartn_y_index].split("(")[0]),
                    float(tmp[cartn_z_index].split("(")[0]),
                ]
                if len(tmp_lbl) > 1:
                    raise ValueError("Check if labesl are correct.", tmp_lbl)
                if (
                    occupancy_index != ""
                    and not float(
                        tmp[occupancy_index].split("(")[0]
                    ).is_integer()
                ):
                    raise ValueError(
                        "Fractional occupancy is not supported.",
                        float(tmp[occupancy_index].split("(")[0]),
                        elem,
                    )
                elements.append(elem)
                coords.append(coord)
            cif_atoms = Atoms(
                lattice_mat=lat.matrix,
                elements=elements,
                coords=coords,
                cartesian=True,
            )
        else:
            raise ValueError(
                "Cannot find atomic coordinate info from cart or frac."
            )
        # frac_coords=list(cif_atoms.frac_coords)
        cif_elements = cif_atoms.elements
        lat = cif_atoms.lattice.matrix
        if len(symm_ops) > 1:
            frac_coords = list(cif_atoms.frac_coords)
            for i in symm_ops:
                for jj, j in enumerate(frac_coords):
                    new_c_coord = get_new_coord_for_xyz_sym(
                        xyz_string=i, frac_coord=j
                    )
                    new_frac_coord = [new_c_coord][0]
                    if not check_duplicate_coords(frac_coords, new_frac_coord):
                        frac_coords.append(new_frac_coord)
                        cif_elements.append(cif_elements[jj])
            new_atoms = Atoms(
                lattice_mat=lat,
                coords=frac_coords,
                elements=cif_elements,
                cartesian=False,
            )
            cif_atoms = new_atoms
        return cif_atoms