Instruction:
Can you generate a brief explanation for the following Python 3 code
def gb_from_parameters(self, rotation_axis, rotation_angle, expand_times=4, vacuum_thickness=0.0,
                           ab_shift=[0, 0], normal=False, ratio=None, plane=None, max_search=20,
                           tol_coi=1.e-8, rm_ratio=0.7, quick_gen=False):

        """
        Args:
           rotation_axis (list): Rotation axis of GB in the form of a list of integer
                e.g.: [1, 1, 0]
           rotation_angle (float, in unit of degree): rotation angle used to generate GB.
                Make sure the angle is accurate enough. You can use the enum* functions
                in this class to extract the accurate angle.
                e.g.: The rotation angle of sigma 3 twist GB with the rotation axis
                [1, 1, 1] and GB plane (1, 1, 1) can be 60.000000000 degree.
                If you do not know the rotation angle, but know the sigma value, we have
                provide the function get_rotation_angle_from_sigma which is able to return
                all the rotation angles of sigma value you provided.
           expand_times (int): The multiple times used to expand one unit grain to larger grain.
                This is used to tune the grain length of GB to warrant that the two GBs in one
                cell do not interact with each other. Default set to 4.
           vacuum_thickness (float, in angstrom): The thickness of vacuum that you want to insert
                between two grains of the GB. Default to 0.
            ab_shift (list of float, in unit of a, b vectors of Gb): in plane shift of two grains
            normal (logic):
                determine if need to require the c axis of top grain (first transformation matrix)
                perperdicular to the surface or not.
                default to false.
            ratio (list of integers):
                    lattice axial ratio.
                    For cubic system, ratio is not needed.
                    For tetragonal system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv = c2/a2. If it is irrational, set it to none.
                    For orthorhombic system, ratio = [mu, lam, mv], list of three integers,
                    that is, mu:lam:mv = c2:b2:a2. If irrational for one axis, set it to None.
                    e.g. mu:lam:mv = c2,None,a2, means b2 is irrational.
                    For rhombohedral system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv is the ratio of (1+2*cos(alpha))/cos(alpha).
                    If irrational, set it to None.
                    For hexagonal system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv = c2/a2. If it is irrational, set it to none.
                    This code also supplies a class method to generate the ratio from the
                    structure (get_ratio). User can also make their own approximation and
                    input the ratio directly.
            plane (list): Grain boundary plane in the form of a list of integers
                e.g.: [1, 2, 3]. If none, we set it as twist GB. The plane will be perpendicular
                to the rotation axis.
            max_search (int): max search for the GB lattice vectors that give the smallest GB
                lattice. If normal is true, also max search the GB c vector that perpendicular
                to the plane. For complex GB, if you want to speed up, you can reduce this value.
                But too small of this value may lead to error.
            tol_coi (float): tolerance to find the coincidence sites. When making approximations to
                the ratio needed to generate the GB, you probably need to increase this tolerance to
                obtain the correct number of coincidence sites. To check the number of coincidence
                sites are correct or not, you can compare the generated Gb object's sigma_from_site_prop
                with enum* sigma values (what user expected by input).
            rm_ratio (float): the criteria to remove the atoms which are too close with each other.
                rm_ratio*bond_length of bulk system is the criteria of bond length, below which the atom
                will be removed. Default to 0.7.
            quick_gen (bool): whether to quickly generate a supercell, if set to true, no need to
                find the smallest cell.

        Returns:
           Grain boundary structure (gb object).
               """
        lat_type = self.lat_type
        # if the initial structure is primitive cell in cubic system,
        # calculate the transformation matrix from its conventional cell
        # to primitive cell, basically for bcc and fcc systems.
        trans_cry = np.eye(3)
        if lat_type == 'c':
            analyzer = SpacegroupAnalyzer(self.initial_structure)
            convention_cell = analyzer.get_conventional_standard_structure()
            vol_ratio = self.initial_structure.volume / convention_cell.volume
            # bcc primitive cell, belong to cubic system
            if abs(vol_ratio - 0.5) < 1.e-3:
                trans_cry = np.array([[0.5, 0.5, -0.5], [-0.5, 0.5, 0.5], [0.5, -0.5, 0.5]])
                logger.info("Make sure this is for cubic with bcc primitive cell")
            # fcc primitive cell, belong to cubic system
            elif abs(vol_ratio - 0.25) < 1.e-3:
                trans_cry = np.array([[0.5, 0.5, 0], [0, 0.5, 0.5], [0.5, 0, 0.5]])
                logger.info("Make sure this is for cubic with fcc primitive cell")
            else:
                logger.info("Make sure this is for cubic with conventional cell")
        elif lat_type == 't':
            logger.info("Make sure this is for tetragonal system")
            if ratio is None:
                logger.info('Make sure this is for irrational c2/a2')
            elif len(ratio) != 2:
                raise RuntimeError('Tetragonal system needs correct c2/a2 ratio')
        elif lat_type == 'o':
            logger.info('Make sure this is for orthorhombic system')
            if ratio is None:
                raise RuntimeError('CSL does not exist if all axial ratios are irrational '
                                   'for an orthorhombic system')
            elif len(ratio) != 3:
                raise RuntimeError('Orthorhombic system needs correct c2:b2:a2 ratio')
        elif lat_type == 'h':
            logger.info('Make sure this is for hexagonal system')
            if ratio is None:
                logger.info('Make sure this is for irrational c2/a2')
            elif len(ratio) != 2:
                raise RuntimeError('Hexagonal system needs correct c2/a2 ratio')
        elif lat_type == 'r':
            logger.info('Make sure this is for rhombohedral system')
            if ratio is None:
                logger.info('Make sure this is for irrational (1+2*cos(alpha)/cos(alpha) ratio')
            elif len(ratio) != 2:
                raise RuntimeError('Rhombohedral system needs correct '
                                   '(1+2*cos(alpha)/cos(alpha) ratio')
        else:
            raise RuntimeError('Lattice type not implemented. This code works for cubic, '
                               'tetragonal, orthorhombic, rhombehedral, hexagonal systems')

        # transform four index notation to three index notation for hexagonal and rhombohedral
        if len(rotation_axis) == 4:
            u1 = rotation_axis[0]
            v1 = rotation_axis[1]
            w1 = rotation_axis[3]
            if lat_type.lower() == 'h':
                u = 2 * u1 + v1
                v = 2 * v1 + u1
                w = w1
                rotation_axis = [u, v, w]
            elif lat_type.lower() == 'r':
                u = 2 * u1 + v1 + w1
                v = v1 + w1 - u1
                w = w1 - 2 * v1 - u1
                rotation_axis = [u, v, w]

        # make sure gcd(rotation_axis)==1
        if reduce(gcd, rotation_axis) != 1:
            rotation_axis = [int(round(x / reduce(gcd, rotation_axis))) for x in rotation_axis]
        # transform four index notation to three index notation for plane
        if plane is not None:
            if len(plane) == 4:
                u1 = plane[0]
                v1 = plane[1]
                w1 = plane[3]
                plane = [u1, v1, w1]
        # set the plane for grain boundary when plane is None.
        if plane is None:
            if lat_type.lower() == 'c':
                plane = rotation_axis
            else:
                if lat_type.lower() == 'h':
                    if ratio is None:
                        c2_a2_ratio = 1
                    else:
                        c2_a2_ratio = ratio[0] / ratio[1]
                    metric = np.array([[1, -0.5, 0], [-0.5, 1, 0], [0, 0, c2_a2_ratio]])
                elif lat_type.lower() == 'r':
                    if ratio is None:
                        cos_alpha = 0.5
                    else:
                        cos_alpha = 1.0 / (ratio[0] / ratio[1] - 2)
                    metric = np.array([[1, cos_alpha, cos_alpha], [cos_alpha, 1, cos_alpha],
                                       [cos_alpha, cos_alpha, 1]])
                elif lat_type.lower() == 't':
                    if ratio is None:
                        c2_a2_ratio = 1
                    else:
                        c2_a2_ratio = ratio[0] / ratio[1]
                    metric = np.array([[1, 0, 0], [0, 1, 0], [0, 0, c2_a2_ratio]])
                elif lat_type.lower() == 'o':
                    for i in range(3):
                        if ratio[i] is None:
                            ratio[i] = 1
                    metric = np.array([[1, 0, 0], [0, ratio[1] / ratio[2], 0],
                                       [0, 0, ratio[0] / ratio[2]]])
                else:
                    raise RuntimeError('Lattice type has not implemented.')

                plane = np.matmul(rotation_axis, metric)
                fractions = [Fraction(x).limit_denominator() for x in plane]
                least_mul = reduce(lcm, [f.denominator for f in fractions])
                plane = [int(round(x * least_mul)) for x in plane]

        if reduce(gcd, plane) != 1:
            index = reduce(gcd, plane)
            plane = [int(round(x / index)) for x in plane]

        t1, t2 = self.get_trans_mat(r_axis=rotation_axis, angle=rotation_angle, normal=normal,
                                    trans_cry=trans_cry, lat_type=lat_type, ratio=ratio,
                                    surface=plane, max_search=max_search, quick_gen=quick_gen)

        # find the join_plane
        if lat_type.lower() != 'c':
            if lat_type.lower() == 'h':
                if ratio is None:
                    mu, mv = [1, 1]
                else:
                    mu, mv = ratio
                trans_cry1 = np.array([[1, 0, 0], [-0.5, np.sqrt(3.0) / 2.0, 0],
                                       [0, 0, np.sqrt(mu / mv)]])
            elif lat_type.lower() == 'r':
                if ratio is None:
                    c2_a2_ratio = 1
                else:
                    mu, mv = ratio
                    c2_a2_ratio = 3.0 / (2 - 6 * mv / mu)
                trans_cry1 = np.array([[0.5, np.sqrt(3.0) / 6.0, 1.0 / 3 * np.sqrt(c2_a2_ratio)],
                                       [-0.5, np.sqrt(3.0) / 6.0, 1.0 / 3 * np.sqrt(c2_a2_ratio)],
                                       [0, -1 * np.sqrt(3.0) / 3.0, 1.0 / 3 * np.sqrt(c2_a2_ratio)]])
            else:
                if lat_type.lower() == 't':
                    if ratio is None:
                        mu, mv = [1, 1]
                    else:
                        mu, mv = ratio
                    lam = mv
                elif lat_type.lower() == 'o':
                    new_ratio = [1 if v is None else v for v in ratio]
                    mu, lam, mv = new_ratio
                trans_cry1 = np.array([[1, 0, 0], [0, np.sqrt(lam / mv), 0], [0, 0, np.sqrt(mu / mv)]])
        else:
            trans_cry1 = trans_cry
        grain_matrix = np.dot(t2, trans_cry1)
        plane_init = np.cross(grain_matrix[0], grain_matrix[1])
        if lat_type.lower() != 'c':
            plane_init = np.dot(plane_init, trans_cry1.T)
        join_plane = self.vec_to_surface(plane_init)

        parent_structure = self.initial_structure.copy()
        # calculate the bond_length in bulk system.
        if len(parent_structure) == 1:
            temp_str = parent_structure.copy()
            temp_str.make_supercell([1, 1, 2])
            distance = temp_str.distance_matrix
        else:
            distance = parent_structure.distance_matrix
        bond_length = np.min(distance[np.nonzero(distance)])

        # top grain
        top_grain = fix_pbc(parent_structure * t1)

        # obtain the smallest oriended cell
        if normal and not quick_gen:
            t_temp = self.get_trans_mat(r_axis=rotation_axis, angle=rotation_angle, normal=False,
                                        trans_cry=trans_cry, lat_type=lat_type, ratio=ratio,
                                        surface=plane, max_search=max_search)
            oriended_unit_cell = fix_pbc(parent_structure * t_temp[0])
            t_matrix = oriended_unit_cell.lattice.matrix
            normal_v_plane = np.cross(t_matrix[0], t_matrix[1])
            unit_normal_v = normal_v_plane / np.linalg.norm(normal_v_plane)
            unit_ab_adjust = (t_matrix[2] - np.dot(unit_normal_v, t_matrix[2]) * unit_normal_v) \
                             / np.dot(unit_normal_v, t_matrix[2])
        else:
            oriended_unit_cell = top_grain.copy()
            unit_ab_adjust = 0.0

        # bottom grain, using top grain's lattice matrix
        bottom_grain = fix_pbc(parent_structure * t2, top_grain.lattice.matrix)

        # label both grains with 'top','bottom','top_incident','bottom_incident'
        n_sites = top_grain.num_sites
        t_and_b = Structure(top_grain.lattice, top_grain.species + bottom_grain.species,
                            list(top_grain.frac_coords) + list(bottom_grain.frac_coords))
        t_and_b_dis = t_and_b.lattice.get_all_distances(t_and_b.frac_coords[0:n_sites],
                                                        t_and_b.frac_coords[n_sites:n_sites * 2])
        index_incident = np.nonzero(t_and_b_dis < np.min(t_and_b_dis) + tol_coi)

        top_labels = []
        for i in range(n_sites):
            if i in index_incident[0]:
                top_labels.append('top_incident')
            else:
                top_labels.append('top')
        bottom_labels = []
        for i in range(n_sites):
            if i in index_incident[1]:
                bottom_labels.append('bottom_incident')
            else:
                bottom_labels.append('bottom')
        top_grain = Structure(Lattice(top_grain.lattice.matrix), top_grain.species,
                              top_grain.frac_coords, site_properties={'grain_label': top_labels})
        bottom_grain = Structure(Lattice(bottom_grain.lattice.matrix), bottom_grain.species,
                                 bottom_grain.frac_coords, site_properties={'grain_label': bottom_labels})

        # expand both grains
        top_grain.make_supercell([1, 1, expand_times])
        bottom_grain.make_supercell([1, 1, expand_times])
        top_grain = fix_pbc(top_grain)
        bottom_grain = fix_pbc(bottom_grain)

        # determine the top-grain location.
        edge_b = 1.0 - max(bottom_grain.frac_coords[:, 2])
        edge_t = 1.0 - max(top_grain.frac_coords[:, 2])
        c_adjust = (edge_t - edge_b) / 2.0

        # construct all species
        all_species = []
        all_species.extend([site.specie for site in bottom_grain])
        all_species.extend([site.specie for site in top_grain])

        half_lattice = top_grain.lattice
        # calculate translation vector, perpendicular to the plane
        normal_v_plane = np.cross(half_lattice.matrix[0], half_lattice.matrix[1])
        unit_normal_v = normal_v_plane / np.linalg.norm(normal_v_plane)
        translation_v = unit_normal_v * vacuum_thickness

        # construct the final lattice
        whole_matrix_no_vac = np.array(half_lattice.matrix)
        whole_matrix_no_vac[2] = half_lattice.matrix[2] * 2
        whole_matrix_with_vac = whole_matrix_no_vac.copy()
        whole_matrix_with_vac[2] = whole_matrix_no_vac[2] + translation_v * 2
        whole_lat = Lattice(whole_matrix_with_vac)

        # construct the coords, move top grain with translation_v
        all_coords = []
        grain_labels = bottom_grain.site_properties['grain_label'] \
                       + top_grain.site_properties['grain_label']
        for site in bottom_grain:
            all_coords.append(site.coords)
        for site in top_grain:
            all_coords.append(site.coords + half_lattice.matrix[2] * (1 + c_adjust) +
                              unit_ab_adjust * np.linalg.norm(half_lattice.matrix[2] * (1 + c_adjust)) +
                              translation_v + ab_shift[0] * whole_matrix_with_vac[0] +
                              ab_shift[1] * whole_matrix_with_vac[1])

        gb_with_vac = Structure(whole_lat, all_species, all_coords,
                                coords_are_cartesian=True,
                                site_properties={'grain_label': grain_labels})
        # merge closer atoms. extract near gb atoms.
        cos_c_norm_plane = np.dot(unit_normal_v, whole_matrix_with_vac[2]) / whole_lat.c
        range_c_len = abs(bond_length / cos_c_norm_plane / whole_lat.c)
        sites_near_gb = []
        sites_away_gb = []
        for site in gb_with_vac.sites:
            if site.frac_coords[2] < range_c_len or site.frac_coords[2] > 1 - range_c_len \
                    or (site.frac_coords[2] > 0.5 - range_c_len and site.frac_coords[2] < 0.5 + range_c_len):
                sites_near_gb.append(site)
            else:
                sites_away_gb.append(site)
        if len(sites_near_gb) >= 1:
            s_near_gb = Structure.from_sites(sites_near_gb)
            s_near_gb.merge_sites(tol=bond_length * rm_ratio, mode='d')
            all_sites = sites_away_gb + s_near_gb.sites
            gb_with_vac = Structure.from_sites(all_sites)

        return GrainBoundary(whole_lat, gb_with_vac.species, gb_with_vac.cart_coords, rotation_axis,
                             rotation_angle, plane, join_plane, self.initial_structure,
                             vacuum_thickness, ab_shift, site_properties=gb_with_vac.site_properties,
                             oriented_unit_cell=oriended_unit_cell,
                             coords_are_cartesian=True)

Response: Generate a new object from parameters in order to generate a new object.


Instruction:
Implement a Python 3 function for
returning the ratio needed for the GB generator input.

Response:
def get_ratio(self, max_denominator=5, index_none=None):
        """
        find the axial ratio needed for GB generator input.
        Args:
            max_denominator (int): the maximum denominator for
                the computed ratio, default to be 5.
            index_none (int): specify the irrational axis.
                0-a, 1-b, 2-c. Only may be needed for orthorombic system.
        Returns:
               axial ratio needed for GB generator (list of integers).

        """
        structure = self.initial_structure
        lat_type = self.lat_type
        if lat_type == 't' or lat_type == 'h':
            # For tetragonal and hexagonal system, ratio = c2 / a2.
            a, c = (structure.lattice.a, structure.lattice.c)
            if c > a:
                frac = Fraction(c ** 2 / a ** 2).limit_denominator(max_denominator)
                ratio = [frac.numerator, frac.denominator]
            else:
                frac = Fraction(a ** 2 / c ** 2).limit_denominator(max_denominator)
                ratio = [frac.denominator, frac.numerator]
        elif lat_type == 'r':
            # For rhombohedral system, ratio = (1 + 2 * cos(alpha)) / cos(alpha).
            cos_alpha = cos(structure.lattice.alpha / 180 * np.pi)
            frac = Fraction((1 + 2 * cos_alpha) / cos_alpha).limit_denominator(max_denominator)
            ratio = [frac.numerator, frac.denominator]
        elif lat_type == 'o':
            # For orthorhombic system, ratio = c2:b2:a2.If irrational for one axis, set it to None.
            ratio = [None] * 3
            lat = (structure.lattice.c, structure.lattice.b, structure.lattice.a)
            index = [0, 1, 2]
            if index_none is None:
                min_index = np.argmin(lat)
                index.pop(min_index)
                frac1 = Fraction(lat[index[0]] ** 2 / lat[min_index] ** 2).limit_denominator(max_denominator)
                frac2 = Fraction(lat[index[1]] ** 2 / lat[min_index] ** 2).limit_denominator(max_denominator)
                com_lcm = lcm(frac1.denominator, frac2.denominator)
                ratio[min_index] = com_lcm
                ratio[index[0]] = frac1.numerator * int(round((com_lcm / frac1.denominator)))
                ratio[index[1]] = frac2.numerator * int(round((com_lcm / frac2.denominator)))
            else:
                index.pop(index_none)
                if (lat[index[0]] > lat[index[1]]):
                    frac = Fraction(lat[index[0]] ** 2 / lat[index[1]] ** 2).limit_denominator(max_denominator)
                    ratio[index[0]] = frac.numerator
                    ratio[index[1]] = frac.denominator
                else:
                    frac = Fraction(lat[index[1]] ** 2 / lat[index[0]] ** 2).limit_denominator(max_denominator)
                    ratio[index[1]] = frac.numerator
                    ratio[index[0]] = frac.denominator
        elif lat_type == 'c':
            raise RuntimeError('Cubic system does not need axial ratio.')
        else:
            raise RuntimeError('Lattice type not implemented.')
        return ratio


Instruction:
Create a Python 3 function to
find the two transformation matrix for each grain from given rotation axis, GB plane, rotation angle and corresponding ratio (see explanation for ratio below). The structure of each grain can be obtained by applying the corresponding transformation matrix to the conventional cell. The algorithm for this code is from reference, Acta Cryst, A32,783(1976). Args: r_axis (list of three integers, e.g. u, v, w or four integers, e.g. u, v, t, w for hex/rho system only): the rotation axis of the grain boundary. angle (float, in unit of degree) : the rotation angle of the grain boundary normal (logic): determine if need to require the c axis of one grain associated with the first transformation matrix perperdicular to the surface or not. default to false. trans_cry (3 by 3 array): if the structure given are primitive cell in cubic system, e.g. bcc or fcc system, trans_cry is the transformation matrix from its conventional cell to the primitive cell. lat_type ( one character): 'c' or 'C': cubic system 't' or 'T': tetragonal system 'o' or 'O': orthorhombic system 'h' or 'H': hexagonal system 'r' or 'R': rhombohedral system default to cubic system ratio (list of integers): lattice axial ratio. For cubic system, ratio is not needed. For tetragonal system, ratio = [mu, mv], list of two integers, that is, mu/mv = c2/a2. If it is irrational, set it to none. For orthorhombic system, ratio = [mu, lam, mv], list of three integers, that is, mu:lam:mv = c2:b2:a2. If irrational for one axis, set it to None. e.g. mu:lam:mv = c2,None,a2, means b2 is irrational. For rhombohedral system, ratio = [mu, mv], list of two integers, that is, mu/mv is the ratio of (1+2*cos(alpha)/cos(alpha). If irrational, set it to None. For hexagonal system, ratio = [mu, mv], list of two integers, that is, mu/mv = c2/a2. If it is irrational, set it to none. surface (list of three integers, e.g. h, k, l or four integers, e.g. h, k, i, l for hex/rho system only): the miller index of grain boundary plane, with the format of [h,k,l] if surface is not given, the default is perpendicular to r_axis, which is a twist grain boundary. max_search (int): max search for the GB lattice vectors that give the smallest GB lattice. If normal is true, also max search the GB c vector that perpendicular to the plane. quick_gen (bool): whether to quickly generate a supercell, if set to true, no need to find the smallest cell. Returns: t1 (3 by 3 integer array): The transformation array for one grain. t2 (3 by 3 integer array): The transformation array for the other grain

Response:
def get_trans_mat(r_axis, angle, normal=False, trans_cry=np.eye(3), lat_type='c',
                      ratio=None, surface=None, max_search=20, quick_gen=False):
        """
        Find the two transformation matrix for each grain from given rotation axis,
        GB plane, rotation angle and corresponding ratio (see explanation for ratio
        below).
        The structure of each grain can be obtained by applying the corresponding
        transformation matrix to the conventional cell.
        The algorithm for this code is from reference, Acta Cryst, A32,783(1976).

        Args:
            r_axis (list of three integers, e.g. u, v, w
                    or four integers, e.g. u, v, t, w for hex/rho system only):
                    the rotation axis of the grain boundary.
            angle (float, in unit of degree) :
                    the rotation angle of the grain boundary
            normal (logic):
                    determine if need to require the c axis of one grain associated with
                    the first transformation matrix perperdicular to the surface or not.
                    default to false.
            trans_cry (3 by 3 array):
                    if the structure given are primitive cell in cubic system, e.g.
                    bcc or fcc system, trans_cry is the transformation matrix from its
                    conventional cell to the primitive cell.
            lat_type ( one character):
                    'c' or 'C': cubic system
                     't' or 'T': tetragonal system
                     'o' or 'O': orthorhombic system
                     'h' or 'H': hexagonal system
                     'r' or 'R': rhombohedral system
                     default to cubic system
            ratio (list of integers):
                    lattice axial ratio.
                    For cubic system, ratio is not needed.
                    For tetragonal system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv = c2/a2. If it is irrational, set it to none.
                    For orthorhombic system, ratio = [mu, lam, mv], list of three integers,
                    that is, mu:lam:mv = c2:b2:a2. If irrational for one axis, set it to None.
                    e.g. mu:lam:mv = c2,None,a2, means b2 is irrational.
                    For rhombohedral system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv is the ratio of (1+2*cos(alpha)/cos(alpha).
                    If irrational, set it to None.
                    For hexagonal system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv = c2/a2. If it is irrational, set it to none.
            surface (list of three integers, e.g. h, k, l
                     or four integers, e.g. h, k, i, l for hex/rho system only):
                    the miller index of grain boundary plane, with the format of [h,k,l]
                    if surface is not given, the default is perpendicular to r_axis, which is
                    a twist grain boundary.
            max_search (int): max search for the GB lattice vectors that give the smallest GB
                lattice. If normal is true, also max search the GB c vector that perpendicular
                to the plane.
            quick_gen (bool): whether to quickly generate a supercell, if set to true, no need to
                find the smallest cell.
        Returns:
            t1 (3 by 3 integer array):
                    The transformation array for one grain.
            t2 (3 by 3 integer array):
                    The transformation array for the other grain
        """
        # transform four index notation to three index notation
        if len(r_axis) == 4:
            u1 = r_axis[0]
            v1 = r_axis[1]
            w1 = r_axis[3]
            if lat_type.lower() == 'h':
                u = 2 * u1 + v1
                v = 2 * v1 + u1
                w = w1
                r_axis = [u, v, w]
            elif lat_type.lower() == 'r':
                u = 2 * u1 + v1 + w1
                v = v1 + w1 - u1
                w = w1 - 2 * v1 - u1
                r_axis = [u, v, w]

        # make sure gcd(r_axis)==1
        if reduce(gcd, r_axis) != 1:
            r_axis = [int(round(x / reduce(gcd, r_axis))) for x in r_axis]

        if surface is not None:
            if len(surface) == 4:
                u1 = surface[0]
                v1 = surface[1]
                w1 = surface[3]
                surface = [u1, v1, w1]
        # set the surface for grain boundary.
        if surface is None:
            if lat_type.lower() == 'c':
                surface = r_axis
            else:
                if lat_type.lower() == 'h':
                    if ratio is None:
                        c2_a2_ratio = 1
                    else:
                        c2_a2_ratio = ratio[0] / ratio[1]
                    metric = np.array([[1, -0.5, 0], [-0.5, 1, 0], [0, 0, c2_a2_ratio]])
                elif lat_type.lower() == 'r':
                    if ratio is None:
                        cos_alpha = 0.5
                    else:
                        cos_alpha = 1.0 / (ratio[0] / ratio[1] - 2)
                    metric = np.array([[1, cos_alpha, cos_alpha], [cos_alpha, 1, cos_alpha],
                                       [cos_alpha, cos_alpha, 1]])
                elif lat_type.lower() == 't':
                    if ratio is None:
                        c2_a2_ratio = 1
                    else:
                        c2_a2_ratio = ratio[0] / ratio[1]
                    metric = np.array([[1, 0, 0], [0, 1, 0], [0, 0, c2_a2_ratio]])
                elif lat_type.lower() == 'o':
                    for i in range(3):
                        if ratio[i] is None:
                            ratio[i] = 1
                    metric = np.array([[1, 0, 0], [0, ratio[1] / ratio[2], 0],
                                       [0, 0, ratio[0] / ratio[2]]])
                else:
                    raise RuntimeError('Lattice type has not implemented.')

                surface = np.matmul(r_axis, metric)
                fractions = [Fraction(x).limit_denominator() for x in surface]
                least_mul = reduce(lcm, [f.denominator for f in fractions])
                surface = [int(round(x * least_mul)) for x in surface]

        if reduce(gcd, surface) != 1:
            index = reduce(gcd, surface)
            surface = [int(round(x / index)) for x in surface]

        if lat_type.lower() == 'h':
            # set the value for u,v,w,mu,mv,m,n,d,x
            # check the reference for the meaning of these parameters
            u, v, w = r_axis
            # make sure mu, mv are coprime integers.
            if ratio is None:
                mu, mv = [1, 1]
                if w != 0:
                    if u != 0 or (v != 0):
                        raise RuntimeError('For irrational c2/a2, CSL only exist for [0,0,1] '
                                           'or [u,v,0] and m = 0')
            else:
                mu, mv = ratio
            if gcd(mu, mv) != 1:
                temp = gcd(mu, mv)
                mu = int(round(mu / temp))
                mv = int(round(mv / temp))
            d = (u ** 2 + v ** 2 - u * v) * mv + w ** 2 * mu
            if abs(angle - 180.0) < 1.e0:
                m = 0
                n = 1
            else:
                fraction = Fraction(np.tan(angle / 2 / 180.0 * np.pi) /
                                    np.sqrt(float(d) / 3.0 / mu)).limit_denominator()
                m = fraction.denominator
                n = fraction.numerator

            # construct the rotation matrix, check reference for details
            r_list = [(u ** 2 * mv - v ** 2 * mv - w ** 2 * mu) * n ** 2 +
                      2 * w * mu * m * n + 3 * mu * m ** 2,
                      (2 * v - u) * u * mv * n ** 2 - 4 * w * mu * m * n,
                      2 * u * w * mu * n ** 2 + 2 * (2 * v - u) * mu * m * n,
                      (2 * u - v) * v * mv * n ** 2 + 4 * w * mu * m * n,
                      (v ** 2 * mv - u ** 2 * mv - w ** 2 * mu) * n ** 2 -
                      2 * w * mu * m * n + 3 * mu * m ** 2,
                      2 * v * w * mu * n ** 2 - 2 * (2 * u - v) * mu * m * n,
                      (2 * u - v) * w * mv * n ** 2 - 3 * v * mv * m * n,
                      (2 * v - u) * w * mv * n ** 2 + 3 * u * mv * m * n,
                      (w ** 2 * mu - u ** 2 * mv - v ** 2 * mv + u * v * mv) *
                      n ** 2 + 3 * mu * m ** 2]
            m = -1 * m
            r_list_inv = [(u ** 2 * mv - v ** 2 * mv - w ** 2 * mu) * n ** 2 +
                          2 * w * mu * m * n + 3 * mu * m ** 2,
                          (2 * v - u) * u * mv * n ** 2 - 4 * w * mu * m * n,
                          2 * u * w * mu * n ** 2 + 2 * (2 * v - u) * mu * m * n,
                          (2 * u - v) * v * mv * n ** 2 + 4 * w * mu * m * n,
                          (v ** 2 * mv - u ** 2 * mv - w ** 2 * mu) * n ** 2 -
                          2 * w * mu * m * n + 3 * mu * m ** 2,
                          2 * v * w * mu * n ** 2 - 2 * (2 * u - v) * mu * m * n,
                          (2 * u - v) * w * mv * n ** 2 - 3 * v * mv * m * n,
                          (2 * v - u) * w * mv * n ** 2 + 3 * u * mv * m * n,
                          (w ** 2 * mu - u ** 2 * mv - v ** 2 * mv + u * v * mv) *
                          n ** 2 + 3 * mu * m ** 2]
            m = -1 * m
            F = 3 * mu * m ** 2 + d * n ** 2
            all_list = r_list + r_list_inv + [F]
            com_fac = reduce(gcd, all_list)
            sigma = F / com_fac
            r_matrix = (np.array(r_list) / com_fac / sigma).reshape(3, 3)
        elif lat_type.lower() == 'r':
            # set the value for u,v,w,mu,mv,m,n,d
            # check the reference for the meaning of these parameters
            u, v, w = r_axis
            # make sure mu, mv are coprime integers.
            if ratio is None:
                mu, mv = [1, 1]
                if u + v + w != 0:
                    if u != v or u != w:
                        raise RuntimeError('For irrational ratio_alpha, CSL only exist for [1,1,1]'
                                           'or [u, v, -(u+v)] and m =0')
            else:
                mu, mv = ratio
            if gcd(mu, mv) != 1:
                temp = gcd(mu, mv)
                mu = int(round(mu / temp))
                mv = int(round(mv / temp))
            d = (u ** 2 + v ** 2 + w ** 2) * (mu - 2 * mv) + \
                2 * mv * (v * w + w * u + u * v)
            if abs(angle - 180.0) < 1.e0:
                m = 0
                n = 1
            else:
                fraction = Fraction(np.tan(angle / 2 / 180.0 * np.pi) /
                                    np.sqrt(float(d) / mu)).limit_denominator()
                m = fraction.denominator
                n = fraction.numerator

            # construct the rotation matrix, check reference for details
            r_list = [(mu - 2 * mv) * (u ** 2 - v ** 2 - w ** 2) * n ** 2 +
                      2 * mv * (v - w) * m * n - 2 * mv * v * w * n ** 2 +
                      mu * m ** 2,
                      2 * (mv * u * n * (w * n + u * n - m) - (mu - mv) *
                           m * w * n + (mu - 2 * mv) * u * v * n ** 2),
                      2 * (mv * u * n * (v * n + u * n + m) + (mu - mv) *
                           m * v * n + (mu - 2 * mv) * w * u * n ** 2),
                      2 * (mv * v * n * (w * n + v * n + m) + (mu - mv) *
                           m * w * n + (mu - 2 * mv) * u * v * n ** 2),
                      (mu - 2 * mv) * (v ** 2 - w ** 2 - u ** 2) * n ** 2 +
                      2 * mv * (w - u) * m * n - 2 * mv * u * w * n ** 2 +
                      mu * m ** 2,
                      2 * (mv * v * n * (v * n + u * n - m) - (mu - mv) *
                           m * u * n + (mu - 2 * mv) * w * v * n ** 2),
                      2 * (mv * w * n * (w * n + v * n - m) - (mu - mv) *
                           m * v * n + (mu - 2 * mv) * w * u * n ** 2),
                      2 * (mv * w * n * (w * n + u * n + m) + (mu - mv) *
                           m * u * n + (mu - 2 * mv) * w * v * n ** 2),
                      (mu - 2 * mv) * (w ** 2 - u ** 2 - v ** 2) * n ** 2 +
                      2 * mv * (u - v) * m * n - 2 * mv * u * v * n ** 2 +
                      mu * m ** 2]
            m = -1 * m
            r_list_inv = [(mu - 2 * mv) * (u ** 2 - v ** 2 - w ** 2) * n ** 2 +
                          2 * mv * (v - w) * m * n - 2 * mv * v * w * n ** 2 +
                          mu * m ** 2,
                          2 * (mv * u * n * (w * n + u * n - m) - (mu - mv) *
                               m * w * n + (mu - 2 * mv) * u * v * n ** 2),
                          2 * (mv * u * n * (v * n + u * n + m) + (mu - mv) *
                               m * v * n + (mu - 2 * mv) * w * u * n ** 2),
                          2 * (mv * v * n * (w * n + v * n + m) + (mu - mv) *
                               m * w * n + (mu - 2 * mv) * u * v * n ** 2),
                          (mu - 2 * mv) * (v ** 2 - w ** 2 - u ** 2) * n ** 2 +
                          2 * mv * (w - u) * m * n - 2 * mv * u * w * n ** 2 +
                          mu * m ** 2,
                          2 * (mv * v * n * (v * n + u * n - m) - (mu - mv) *
                               m * u * n + (mu - 2 * mv) * w * v * n ** 2),
                          2 * (mv * w * n * (w * n + v * n - m) - (mu - mv) *
                               m * v * n + (mu - 2 * mv) * w * u * n ** 2),
                          2 * (mv * w * n * (w * n + u * n + m) + (mu - mv) *
                               m * u * n + (mu - 2 * mv) * w * v * n ** 2),
                          (mu - 2 * mv) * (w ** 2 - u ** 2 - v ** 2) * n ** 2 +
                          2 * mv * (u - v) * m * n - 2 * mv * u * v * n ** 2 +
                          mu * m ** 2]
            m = -1 * m
            F = mu * m ** 2 + d * n ** 2
            all_list = r_list_inv + r_list + [F]
            com_fac = reduce(gcd, all_list)
            sigma = F / com_fac
            r_matrix = (np.array(r_list) / com_fac / sigma).reshape(3, 3)
        else:
            u, v, w = r_axis
            if lat_type.lower() == 'c':
                mu = 1
                lam = 1
                mv = 1
            elif lat_type.lower() == 't':
                if ratio is None:
                    mu, mv = [1, 1]
                    if w != 0:
                        if u != 0 or (v != 0):
                            raise RuntimeError('For irrational c2/a2, CSL only exist for [0,0,1] '
                                               'or [u,v,0] and m = 0')
                else:
                    mu, mv = ratio
                lam = mv
            elif lat_type.lower() == 'o':
                if None in ratio:
                    mu, lam, mv = ratio
                    non_none = [i for i in ratio if i is not None]
                    if len(non_none) < 2:
                        raise RuntimeError('No CSL exist for two irrational numbers')
                    non1, non2 = non_none
                    if mu is None:
                        lam = non1
                        mv = non2
                        mu = 1
                        if w != 0:
                            if u != 0 or (v != 0):
                                raise RuntimeError('For irrational c2, CSL only exist for [0,0,1] '
                                                   'or [u,v,0] and m = 0')
                    elif lam is None:
                        mu = non1
                        mv = non2
                        lam = 1
                        if v != 0:
                            if u != 0 or (w != 0):
                                raise RuntimeError('For irrational b2, CSL only exist for [0,1,0] '
                                                   'or [u,0,w] and m = 0')
                    elif mv is None:
                        mu = non1
                        lam = non2
                        mv = 1
                        if u != 0:
                            if w != 0 or (v != 0):
                                raise RuntimeError('For irrational a2, CSL only exist for [1,0,0] '
                                                   'or [0,v,w] and m = 0')
                else:
                    mu, lam, mv = ratio
                    if u == 0 and v == 0:
                        mu = 1
                    if u == 0 and w == 0:
                        lam = 1
                    if v == 0 and w == 0:
                        mv = 1

            # make sure mu, lambda, mv are coprime integers.
            if reduce(gcd, [mu, lam, mv]) != 1:
                temp = reduce(gcd, [mu, lam, mv])
                mu = int(round(mu / temp))
                mv = int(round(mv / temp))
                lam = int(round(lam / temp))
            d = (mv * u ** 2 + lam * v ** 2) * mv + w ** 2 * mu * mv
            if abs(angle - 180.0) < 1.e0:
                m = 0
                n = 1
            else:
                fraction = Fraction(np.tan(angle / 2 / 180.0 * np.pi) /
                                    np.sqrt(d / mu / lam)).limit_denominator()
                m = fraction.denominator
                n = fraction.numerator
            r_list = [(u ** 2 * mv * mv - lam * v ** 2 * mv -
                       w ** 2 * mu * mv) * n ** 2 + lam * mu * m ** 2,
                      2 * lam * (v * u * mv * n ** 2 - w * mu * m * n),
                      2 * mu * (u * w * mv * n ** 2 + v * lam * m * n),
                      2 * mv * (u * v * mv * n ** 2 + w * mu * m * n),
                      (v ** 2 * mv * lam - u ** 2 * mv * mv -
                       w ** 2 * mu * mv) * n ** 2 + lam * mu * m ** 2,
                      2 * mv * mu * (v * w * n ** 2 - u * m * n),
                      2 * mv * (u * w * mv * n ** 2 - v * lam * m * n),
                      2 * lam * mv * (v * w * n ** 2 + u * m * n),
                      (w ** 2 * mu * mv - u ** 2 * mv * mv -
                       v ** 2 * mv * lam) * n ** 2 + lam * mu * m ** 2]
            m = -1 * m
            r_list_inv = [(u ** 2 * mv * mv - lam * v ** 2 * mv -
                           w ** 2 * mu * mv) * n ** 2 + lam * mu * m ** 2,
                          2 * lam * (v * u * mv * n ** 2 - w * mu * m * n),
                          2 * mu * (u * w * mv * n ** 2 + v * lam * m * n),
                          2 * mv * (u * v * mv * n ** 2 + w * mu * m * n),
                          (v ** 2 * mv * lam - u ** 2 * mv * mv -
                           w ** 2 * mu * mv) * n ** 2 + lam * mu * m ** 2,
                          2 * mv * mu * (v * w * n ** 2 - u * m * n),
                          2 * mv * (u * w * mv * n ** 2 - v * lam * m * n),
                          2 * lam * mv * (v * w * n ** 2 + u * m * n),
                          (w ** 2 * mu * mv - u ** 2 * mv * mv -
                           v ** 2 * mv * lam) * n ** 2 + lam * mu * m ** 2]
            m = -1 * m
            F = mu * lam * m ** 2 + d * n ** 2
            all_list = r_list + r_list_inv + [F]
            com_fac = reduce(gcd, all_list)
            sigma = F / com_fac
            r_matrix = (np.array(r_list) / com_fac / sigma).reshape(3, 3)

        if (sigma > 1000):
            raise RuntimeError('Sigma >1000 too large. Are you sure what you are doing, '
                               'Please check the GB if exist')
        # transform surface, r_axis, r_matrix in terms of primitive lattice
        surface = np.matmul(surface, np.transpose(trans_cry))
        fractions = [Fraction(x).limit_denominator() for x in surface]
        least_mul = reduce(lcm, [f.denominator for f in fractions])
        surface = [int(round(x * least_mul)) for x in surface]
        if reduce(gcd, surface) != 1:
            index = reduce(gcd, surface)
            surface = [int(round(x / index)) for x in surface]
        r_axis = np.rint(np.matmul(r_axis, np.linalg.inv(trans_cry))).astype(int)
        if reduce(gcd, r_axis) != 1:
            r_axis = [int(round(x / reduce(gcd, r_axis))) for x in r_axis]
        r_matrix = np.dot(np.dot(np.linalg.inv(trans_cry.T), r_matrix), trans_cry.T)
        # set one vector of the basis to the rotation axis direction, and
        # obtain the corresponding transform matrix
        eye = np.eye(3, dtype=np.int)
        for h in range(3):
            if abs(r_axis[h]) != 0:
                eye[h] = np.array(r_axis)
                k = h + 1 if h + 1 < 3 else abs(2 - h)
                l = h + 2 if h + 2 < 3 else abs(1 - h)
                break
        trans = eye.T
        new_rot = np.array(r_matrix)

        # with the rotation matrix to construct the CSL lattice, check reference for details
        fractions = [Fraction(x).limit_denominator() for x in new_rot[:, k]]
        least_mul = reduce(lcm, [f.denominator for f in fractions])
        scale = np.zeros((3, 3))
        scale[h, h] = 1
        scale[k, k] = least_mul
        scale[l, l] = sigma / least_mul
        for i in range(least_mul):
            check_int = i * new_rot[:, k] + (sigma / least_mul) * new_rot[:, l]
            if all([np.round(x, 5).is_integer() for x in list(check_int)]):
                n_final = i
                break
        try:
            n_final
        except NameError:
            raise RuntimeError('Something is wrong. Check if this GB exists or not')
        scale[k, l] = n_final
        # each row of mat_csl is the CSL lattice vector
        csl_init = np.rint(np.dot(np.dot(r_matrix, trans), scale)).astype(int).T
        if abs(r_axis[h]) > 1:
            csl_init = GrainBoundaryGenerator.reduce_mat(np.array(csl_init), r_axis[h], r_matrix)
        csl = np.rint(Lattice(csl_init).get_niggli_reduced_lattice().matrix).astype(int)

        # find the best slab supercell in terms of the conventional cell from the csl lattice,
        # which is the transformation matrix

        # now trans_cry is the transformation matrix from crystal to cartesian coordinates.
        # for cubic, do not need to change.
        if lat_type.lower() != 'c':
            if lat_type.lower() == 'h':
                trans_cry = np.array([[1, 0, 0], [-0.5, np.sqrt(3.0) / 2.0, 0],
                                      [0, 0, np.sqrt(mu / mv)]])
            elif lat_type.lower() == 'r':
                if ratio is None:
                    c2_a2_ratio = 1
                else:
                    c2_a2_ratio = 3.0 / (2 - 6 * mv / mu)
                trans_cry = np.array([[0.5, np.sqrt(3.0) / 6.0, 1.0 / 3 * np.sqrt(c2_a2_ratio)],
                                      [-0.5, np.sqrt(3.0) / 6.0, 1.0 / 3 * np.sqrt(c2_a2_ratio)],
                                      [0, -1 * np.sqrt(3.0) / 3.0, 1.0 / 3 * np.sqrt(c2_a2_ratio)]])
            else:
                trans_cry = np.array([[1, 0, 0], [0, np.sqrt(lam / mv), 0], [0, 0, np.sqrt(mu / mv)]])
        t1_final = GrainBoundaryGenerator.slab_from_csl(csl, surface, normal, trans_cry, max_search=max_search,
                                                        quick_gen=quick_gen)
        t2_final = np.array(np.rint(np.dot(t1_final, np.linalg.inv(r_matrix.T)))).astype(int)
        return t1_final, t2_final


Instruction:
How would you explain what the following Python 3 function does
def enum_sigma_cubic(cutoff, r_axis):
        sigmas = {}
        # make sure gcd(r_axis)==1
        if reduce(gcd, r_axis) != 1:
            r_axis = [int(round(x / reduce(gcd, r_axis))) for x in r_axis]

        # count the number of odds in r_axis
        odd_r = len(list(filter(lambda x: x % 2 == 1, r_axis)))
        # Compute the max n we need to enumerate.
        if odd_r == 3:
            a_max = 4
        elif odd_r == 0:
            a_max = 1
        else:
            a_max = 2
        n_max = int(np.sqrt(cutoff * a_max / sum(np.array(r_axis) ** 2)))
        # enumerate all possible n, m to give possible sigmas within the cutoff.
        for n_loop in range(1, n_max + 1):
            n = n_loop
            m_max = int(np.sqrt(cutoff * a_max - n ** 2 * sum(np.array(r_axis) ** 2)))
            for m in range(0, m_max + 1):
                if gcd(m, n) == 1 or m == 0:
                    if m == 0:
                        n = 1
                    else:
                        n = n_loop
                    # construct the quadruple [m, U,V,W], count the number of odds in
                    # quadruple to determine the parameter a, refer to the reference
                    quadruple = [m] + [x * n for x in r_axis]
                    odd_qua = len(list(filter(lambda x: x % 2 == 1, quadruple)))
                    if odd_qua == 4:
                        a = 4
                    elif odd_qua == 2:
                        a = 2
                    else:
                        a = 1
                    sigma = int(round((m ** 2 + n ** 2 * sum(np.array(r_axis) ** 2)) / a))
                    if (sigma <= cutoff) and (sigma > 1):
                        if sigma not in list(sigmas.keys()):
                            if m == 0:
                                angle = 180.0
                            else:
                                angle = 2 * np.arctan(n * np.sqrt(sum(np.array(r_axis) ** 2)) / m) \
                                        / np.pi * 180
                            sigmas[sigma] = [angle]
                        else:
                            if m == 0:
                                angle = 180.0
                            else:
                                angle = 2 * np.arctan(n * np.sqrt(sum(np.array(r_axis) ** 2)) / m) \
                                        / np.pi * 180
                            if angle not in sigmas[sigma]:
                                sigmas[sigma].append(angle)
        return sigmas

Response: Enumerate all possible sigma values and corresponding rotation angles within a known cubic system.


Instruction:
How would you explain what the following Python 3 function does
def enum_sigma_rho(cutoff, r_axis, ratio_alpha):
        sigmas = {}
        # transform four index notation to three index notation
        if len(r_axis) == 4:
            u1 = r_axis[0]
            v1 = r_axis[1]
            w1 = r_axis[3]
            u = 2 * u1 + v1 + w1
            v = v1 + w1 - u1
            w = w1 - 2 * v1 - u1
            r_axis = [u, v, w]
        # make sure gcd(r_axis)==1
        if reduce(gcd, r_axis) != 1:
            r_axis = [int(round(x / reduce(gcd, r_axis))) for x in r_axis]
        u, v, w = r_axis
        # make sure mu, mv are coprime integers.
        if ratio_alpha is None:
            mu, mv = [1, 1]
            if u + v + w != 0:
                if u != v or u != w:
                    raise RuntimeError('For irrational ratio_alpha, CSL only exist for [1,1,1]'
                                       'or [u, v, -(u+v)] and m =0')
        else:
            mu, mv = ratio_alpha
            if gcd(mu, mv) != 1:
                temp = gcd(mu, mv)
                mu = int(round(mu / temp))
                mv = int(round(mv / temp))

        # refer to the meaning of d in reference
        d = (u ** 2 + v ** 2 + w ** 2) * (mu - 2 * mv) + \
            2 * mv * (v * w + w * u + u * v)
        # Compute the max n we need to enumerate.
        n_max = int(np.sqrt((cutoff * abs(4 * mu * (mu - 3 * mv))) / abs(d)))

        # Enumerate all possible n, m to give possible sigmas within the cutoff.
        for n in range(1, n_max + 1):
            if ratio_alpha is None and u + v + w == 0:
                m_max = 0
            else:
                m_max = int(np.sqrt((cutoff * abs(4 * mu * (mu - 3 * mv)) - n ** 2 * d) / (mu)))
            for m in range(0, m_max + 1):
                if gcd(m, n) == 1 or m == 0:
                    # construct the rotation matrix, refer to the reference
                    R_list = [(mu - 2 * mv) * (u ** 2 - v ** 2 - w ** 2) * n ** 2 +
                              2 * mv * (v - w) * m * n - 2 * mv * v * w * n ** 2 +
                              mu * m ** 2,
                              2 * (mv * u * n * (w * n + u * n - m) - (mu - mv) *
                                   m * w * n + (mu - 2 * mv) * u * v * n ** 2),
                              2 * (mv * u * n * (v * n + u * n + m) + (mu - mv) *
                                   m * v * n + (mu - 2 * mv) * w * u * n ** 2),
                              2 * (mv * v * n * (w * n + v * n + m) + (mu - mv) *
                                   m * w * n + (mu - 2 * mv) * u * v * n ** 2),
                              (mu - 2 * mv) * (v ** 2 - w ** 2 - u ** 2) * n ** 2 +
                              2 * mv * (w - u) * m * n - 2 * mv * u * w * n ** 2 +
                              mu * m ** 2,
                              2 * (mv * v * n * (v * n + u * n - m) - (mu - mv) *
                                   m * u * n + (mu - 2 * mv) * w * v * n ** 2),
                              2 * (mv * w * n * (w * n + v * n - m) - (mu - mv) *
                                   m * v * n + (mu - 2 * mv) * w * u * n ** 2),
                              2 * (mv * w * n * (w * n + u * n + m) + (mu - mv) *
                                   m * u * n + (mu - 2 * mv) * w * v * n ** 2),
                              (mu - 2 * mv) * (w ** 2 - u ** 2 - v ** 2) * n ** 2 +
                              2 * mv * (u - v) * m * n - 2 * mv * u * v * n ** 2 +
                              mu * m ** 2]
                    m = -1 * m
                    # inverse of the rotation matrix
                    R_list_inv = [(mu - 2 * mv) * (u ** 2 - v ** 2 - w ** 2) * n ** 2 +
                                  2 * mv * (v - w) * m * n - 2 * mv * v * w * n ** 2 +
                                  mu * m ** 2,
                                  2 * (mv * u * n * (w * n + u * n - m) - (mu - mv) *
                                       m * w * n + (mu - 2 * mv) * u * v * n ** 2),
                                  2 * (mv * u * n * (v * n + u * n + m) + (mu - mv) *
                                       m * v * n + (mu - 2 * mv) * w * u * n ** 2),
                                  2 * (mv * v * n * (w * n + v * n + m) + (mu - mv) *
                                       m * w * n + (mu - 2 * mv) * u * v * n ** 2),
                                  (mu - 2 * mv) * (v ** 2 - w ** 2 - u ** 2) * n ** 2 +
                                  2 * mv * (w - u) * m * n - 2 * mv * u * w * n ** 2 +
                                  mu * m ** 2,
                                  2 * (mv * v * n * (v * n + u * n - m) - (mu - mv) *
                                       m * u * n + (mu - 2 * mv) * w * v * n ** 2),
                                  2 * (mv * w * n * (w * n + v * n - m) - (mu - mv) *
                                       m * v * n + (mu - 2 * mv) * w * u * n ** 2),
                                  2 * (mv * w * n * (w * n + u * n + m) + (mu - mv) *
                                       m * u * n + (mu - 2 * mv) * w * v * n ** 2),
                                  (mu - 2 * mv) * (w ** 2 - u ** 2 - v ** 2) * n ** 2 +
                                  2 * mv * (u - v) * m * n - 2 * mv * u * v * n ** 2 +
                                  mu * m ** 2]
                    m = -1 * m
                    F = mu * m ** 2 + d * n ** 2
                    all_list = R_list_inv + R_list + [F]
                    # Compute the max common factors for the elements of the rotation matrix
                    #  and its inverse.
                    com_fac = reduce(gcd, all_list)
                    sigma = int(round(abs(F / com_fac)))
                    if (sigma <= cutoff) and (sigma > 1):
                        if sigma not in list(sigmas.keys()):
                            if m == 0:
                                angle = 180.0
                            else:
                                angle = 2 * np.arctan(n / m * np.sqrt(d / mu)) \
                                        / np.pi * 180
                            sigmas[sigma] = [angle]
                        else:
                            if m == 0:
                                angle = 180
                            else:
                                angle = 2 * np.arctan(n / m * np.sqrt(d / mu)) \
                                        / np.pi * 180.0
                            if angle not in sigmas[sigma]:
                                sigmas[sigma].append(angle)
            if m_max == 0:
                break
        return sigmas

Response: Enumerate all possible sigma values and corresponding rotation angles within a rhombohedral system.


Instruction:
Write a Python 3 function for
enumerating all possible sigma values and corresponding rotation angles within a tetragonal system.

Response:
def enum_sigma_tet(cutoff, r_axis, c2_a2_ratio):
        """
        Find all possible sigma values and corresponding rotation angles
        within a sigma value cutoff with known rotation axis in tetragonal system.
        The algorithm for this code is from reference, Acta Cryst, B46,117(1990)

        Args:
            cutoff (integer): the cutoff of sigma values.
            r_axis (list of three integers, e.g. u, v, w):
                    the rotation axis of the grain boundary, with the format of [u,v,w].
            c2_a2_ratio (list of two integers, e.g. mu, mv):
                    mu/mv is the square of the tetragonal axial ratio with rational number.
                    if irrational, set c2_a2_ratio = None
        Returns:
            sigmas (dict):
                    dictionary with keys as the possible integer sigma values
                    and values as list of the possible rotation angles to the
                    corresponding sigma values.
                    e.g. the format as
                    {sigma1: [angle11,angle12,...], sigma2: [angle21, angle22,...],...}
                    Note: the angles are the rotation angle of one grain respect to the
                    other grain.
                    When generate the microstructure of the grain boundary using these
                    angles, you need to analyze the symmetry of the structure. Different
                    angles may result in equivalent microstructures.

        """
        sigmas = {}
        # make sure gcd(r_axis)==1
        if reduce(gcd, r_axis) != 1:
            r_axis = [int(round(x / reduce(gcd, r_axis))) for x in r_axis]

        u, v, w = r_axis

        # make sure mu, mv are coprime integers.
        if c2_a2_ratio is None:
            mu, mv = [1, 1]
            if w != 0:
                if u != 0 or (v != 0):
                    raise RuntimeError('For irrational c2/a2, CSL only exist for [0,0,1] '
                                       'or [u,v,0] and m = 0')
        else:
            mu, mv = c2_a2_ratio
            if gcd(mu, mv) != 1:
                temp = gcd(mu, mv)
                mu = int(round(mu / temp))
                mv = int(round(mv / temp))

        # refer to the meaning of d in reference
        d = (u ** 2 + v ** 2) * mv + w ** 2 * mu

        # Compute the max n we need to enumerate.
        n_max = int(np.sqrt((cutoff * 4 * mu * mv) / d))

        # Enumerate all possible n, m to give possible sigmas within the cutoff.
        for n in range(1, n_max + 1):
            if c2_a2_ratio is None and w == 0:
                m_max = 0
            else:
                m_max = int(np.sqrt((cutoff * 4 * mu * mv - n ** 2 * d) / mu))
            for m in range(0, m_max + 1):
                if gcd(m, n) == 1 or m == 0:
                    # construct the rotation matrix, refer to the reference
                    R_list = [(u ** 2 * mv - v ** 2 * mv - w ** 2 * mu) * n ** 2 +
                              mu * m ** 2,
                              2 * v * u * mv * n ** 2 - 2 * w * mu * m * n,
                              2 * u * w * mu * n ** 2 + 2 * v * mu * m * n,
                              2 * u * v * mv * n ** 2 + 2 * w * mu * m * n,
                              (v ** 2 * mv - u ** 2 * mv - w ** 2 * mu) * n ** 2 +
                              mu * m ** 2,
                              2 * v * w * mu * n ** 2 - 2 * u * mu * m * n,
                              2 * u * w * mv * n ** 2 - 2 * v * mv * m * n,
                              2 * v * w * mv * n ** 2 + 2 * u * mv * m * n,
                              (w ** 2 * mu - u ** 2 * mv - v ** 2 * mv) * n ** 2 +
                              mu * m ** 2]
                    m = -1 * m
                    # inverse of rotation matrix
                    R_list_inv = [(u ** 2 * mv - v ** 2 * mv - w ** 2 * mu) * n ** 2 +
                                  mu * m ** 2,
                                  2 * v * u * mv * n ** 2 - 2 * w * mu * m * n,
                                  2 * u * w * mu * n ** 2 + 2 * v * mu * m * n,
                                  2 * u * v * mv * n ** 2 + 2 * w * mu * m * n,
                                  (v ** 2 * mv - u ** 2 * mv - w ** 2 * mu) * n ** 2 +
                                  mu * m ** 2,
                                  2 * v * w * mu * n ** 2 - 2 * u * mu * m * n,
                                  2 * u * w * mv * n ** 2 - 2 * v * mv * m * n,
                                  2 * v * w * mv * n ** 2 + 2 * u * mv * m * n,
                                  (w ** 2 * mu - u ** 2 * mv - v ** 2 * mv) * n ** 2 +
                                  mu * m ** 2]
                    m = -1 * m
                    F = mu * m ** 2 + d * n ** 2
                    all_list = R_list + R_list_inv + [F]
                    # Compute the max common factors for the elements of the rotation matrix
                    #  and its inverse.
                    com_fac = reduce(gcd, all_list)
                    sigma = int(round((mu * m ** 2 + d * n ** 2) / com_fac))
                    if (sigma <= cutoff) and (sigma > 1):
                        if sigma not in list(sigmas.keys()):
                            if m == 0:
                                angle = 180.0
                            else:
                                angle = 2 * np.arctan(n / m * np.sqrt(d / mu)) \
                                        / np.pi * 180
                            sigmas[sigma] = [angle]
                        else:
                            if m == 0:
                                angle = 180.0
                            else:
                                angle = 2 * np.arctan(n / m * np.sqrt(d / mu)) \
                                        / np.pi * 180
                            if angle not in sigmas[sigma]:
                                sigmas[sigma].append(angle)
            if m_max == 0:
                break

        return sigmas


Instruction:
Can you tell what is the following Python 3 function doing
def enum_sigma_ort(cutoff, r_axis, c2_b2_a2_ratio):
        sigmas = {}
        # make sure gcd(r_axis)==1
        if reduce(gcd, r_axis) != 1:
            r_axis = [int(round(x / reduce(gcd, r_axis))) for x in r_axis]

        u, v, w = r_axis
        # make sure mu, lambda, mv are coprime integers.
        if None in c2_b2_a2_ratio:
            mu, lam, mv = c2_b2_a2_ratio
            non_none = [i for i in c2_b2_a2_ratio if i is not None]
            if len(non_none) < 2:
                raise RuntimeError('No CSL exist for two irrational numbers')
            non1, non2 = non_none
            if reduce(gcd, non_none) != 1:
                temp = reduce(gcd, non_none)
                non1 = int(round(non1 / temp))
                non2 = int(round(non2 / temp))
            if mu is None:
                lam = non1
                mv = non2
                mu = 1
                if w != 0:
                    if u != 0 or (v != 0):
                        raise RuntimeError('For irrational c2, CSL only exist for [0,0,1] '
                                           'or [u,v,0] and m = 0')
            elif lam is None:
                mu = non1
                mv = non2
                lam = 1
                if v != 0:
                    if u != 0 or (w != 0):
                        raise RuntimeError('For irrational b2, CSL only exist for [0,1,0] '
                                           'or [u,0,w] and m = 0')
            elif mv is None:
                mu = non1
                lam = non2
                mv = 1
                if u != 0:
                    if w != 0 or (v != 0):
                        raise RuntimeError('For irrational a2, CSL only exist for [1,0,0] '
                                           'or [0,v,w] and m = 0')
        else:
            mu, lam, mv = c2_b2_a2_ratio
            if reduce(gcd, c2_b2_a2_ratio) != 1:
                temp = reduce(gcd, c2_b2_a2_ratio)
                mu = int(round(mu / temp))
                mv = int(round(mv / temp))
                lam = int(round(lam / temp))
            if u == 0 and v == 0:
                mu = 1
            if u == 0 and w == 0:
                lam = 1
            if v == 0 and w == 0:
                mv = 1
        # refer to the meaning of d in reference
        d = (mv * u ** 2 + lam * v ** 2) * mv + w ** 2 * mu * mv

        # Compute the max n we need to enumerate.
        n_max = int(np.sqrt((cutoff * 4 * mu * mv * mv * lam) / d))
        # Enumerate all possible n, m to give possible sigmas within the cutoff.
        for n in range(1, n_max + 1):
            mu_temp, lam_temp, mv_temp = c2_b2_a2_ratio
            if (mu_temp is None and w == 0) or (lam_temp is None and v == 0) \
                    or (mv_temp is None and u == 0):
                m_max = 0
            else:
                m_max = int(np.sqrt((cutoff * 4 * mu * mv * lam * mv -
                                     n ** 2 * d) / mu / lam))
            for m in range(0, m_max + 1):

                if gcd(m, n) == 1 or m == 0:
                    # construct the rotation matrix, refer to the reference
                    R_list = [(u ** 2 * mv * mv - lam * v ** 2 * mv -
                               w ** 2 * mu * mv) * n ** 2 + lam * mu * m ** 2,
                              2 * lam * (v * u * mv * n ** 2 - w * mu * m * n),
                              2 * mu * (u * w * mv * n ** 2 + v * lam * m * n),
                              2 * mv * (u * v * mv * n ** 2 + w * mu * m * n),
                              (v ** 2 * mv * lam - u ** 2 * mv * mv -
                               w ** 2 * mu * mv) * n ** 2 + lam * mu * m ** 2,
                              2 * mv * mu * (v * w * n ** 2 - u * m * n),
                              2 * mv * (u * w * mv * n ** 2 - v * lam * m * n),
                              2 * lam * mv * (v * w * n ** 2 + u * m * n),
                              (w ** 2 * mu * mv - u ** 2 * mv * mv -
                               v ** 2 * mv * lam) * n ** 2 + lam * mu * m ** 2]
                    m = -1 * m
                    # inverse of rotation matrix
                    R_list_inv = [(u ** 2 * mv * mv - lam * v ** 2 * mv -
                                   w ** 2 * mu * mv) * n ** 2 + lam * mu * m ** 2,
                                  2 * lam * (v * u * mv * n ** 2 - w * mu * m * n),
                                  2 * mu * (u * w * mv * n ** 2 + v * lam * m * n),
                                  2 * mv * (u * v * mv * n ** 2 + w * mu * m * n),
                                  (v ** 2 * mv * lam - u ** 2 * mv * mv -
                                   w ** 2 * mu * mv) * n ** 2 + lam * mu * m ** 2,
                                  2 * mv * mu * (v * w * n ** 2 - u * m * n),
                                  2 * mv * (u * w * mv * n ** 2 - v * lam * m * n),
                                  2 * lam * mv * (v * w * n ** 2 + u * m * n),
                                  (w ** 2 * mu * mv - u ** 2 * mv * mv -
                                   v ** 2 * mv * lam) * n ** 2 + lam * mu * m ** 2]
                    m = -1 * m
                    F = mu * lam * m ** 2 + d * n ** 2
                    all_list = R_list + R_list_inv + [F]
                    # Compute the max common factors for the elements of the rotation matrix
                    #  and its inverse.
                    com_fac = reduce(gcd, all_list)
                    sigma = int(round((mu * lam * m ** 2 + d * n ** 2) / com_fac))
                    if (sigma <= cutoff) and (sigma > 1):
                        if sigma not in list(sigmas.keys()):
                            if m == 0:
                                angle = 180.0
                            else:
                                angle = 2 * np.arctan(n / m * np.sqrt(d / mu / lam)) \
                                        / np.pi * 180
                            sigmas[sigma] = [angle]
                        else:
                            if m == 0:
                                angle = 180.0
                            else:
                                angle = 2 * np.arctan(n / m * np.sqrt(d / mu / lam)) \
                                        / np.pi * 180
                            if angle not in sigmas[sigma]:
                                sigmas[sigma].append(angle)
            if m_max == 0:
                break

        return sigmas

Response: Enumerate all possible sigma values and corresponding rotation angles within a given cutoff with known rotation axis.


Instruction:
Implement a function in Python 3 to
enumerate all possible plane combinations for a given cubic system.

Response:
def enum_possible_plane_cubic(plane_cutoff, r_axis, r_angle):
        """
        Find all possible plane combinations for GBs given a rotaion axis and angle for
        cubic system, and classify them to different categories, including 'Twist',
        'Symmetric tilt', 'Normal tilt', 'Mixed' GBs.

        Args:
            plane_cutoff (integer): the cutoff of plane miller index.
            r_axis (list of three integers, e.g. u, v, w):
                    the rotation axis of the grain boundary, with the format of [u,v,w].
            r_angle (float): rotation angle of the GBs.
        Returns:
            all_combinations (dict):
                    dictionary with keys as GB type, e.g. 'Twist','Symmetric tilt',etc.
                    and values as the combination of the two plane miller index
                     (GB plane and joining plane).
        """
        all_combinations = {}
        all_combinations['Symmetric tilt'] = []
        all_combinations['Twist'] = []
        all_combinations['Normal tilt'] = []
        all_combinations['Mixed'] = []
        sym_plane = symm_group_cubic([[1, 0, 0], [1, 1, 0]])
        j = np.arange(0, plane_cutoff + 1)
        combination = []
        for i in itertools.product(j, repeat=3):
            if sum(abs(np.array(i))) != 0:
                combination.append(list(i))
            if len(np.nonzero(i)[0]) == 3:
                for i1 in range(3):
                    new_i = list(i).copy()
                    new_i[i1] = -1 * new_i[i1]
                    combination.append(new_i)
            elif len(np.nonzero(i)[0]) == 2:
                new_i = list(i).copy()
                new_i[np.nonzero(i)[0][0]] = -1 * new_i[np.nonzero(i)[0][0]]
                combination.append(new_i)
        miller = np.array(combination)
        miller = miller[np.argsort(np.linalg.norm(miller, axis=1))]
        for i, val in enumerate(miller):
            if reduce(gcd, val) == 1:
                matrix = GrainBoundaryGenerator.get_trans_mat(r_axis, r_angle, surface=val, quick_gen=True)
                vec = np.cross(matrix[1][0], matrix[1][1])
                miller2 = GrainBoundaryGenerator.vec_to_surface(vec)
                if np.all(np.abs(np.array(miller2)) <= plane_cutoff):
                    cos_1 = abs(np.dot(val, r_axis) / np.linalg.norm(val) / np.linalg.norm(r_axis))
                    if 1 - cos_1 < 1.e-5:
                        all_combinations['Twist'].append([list(val), miller2])
                    elif cos_1 < 1.e-8:
                        sym_tilt = False
                        if np.sum(np.abs(val)) == np.sum(np.abs(miller2)):
                            ave = (np.array(val) + np.array(miller2)) / 2
                            ave1 = (np.array(val) - np.array(miller2)) / 2
                            for plane in sym_plane:
                                cos_2 = abs(np.dot(ave, plane) / np.linalg.norm(ave) / np.linalg.norm(plane))
                                cos_3 = abs(np.dot(ave1, plane) / np.linalg.norm(ave1) / np.linalg.norm(plane))
                                if 1 - cos_2 < 1.e-5 or 1 - cos_3 < 1.e-5:
                                    all_combinations['Symmetric tilt'].append([list(val), miller2])
                                    sym_tilt = True
                                    break
                        if not sym_tilt:
                            all_combinations['Normal tilt'].append([list(val), miller2])
                    else:
                        all_combinations['Mixed'].append([list(val), miller2])
        return all_combinations


Instruction:
Write a Python 3 script to
return the rotation angle corresponding to the given sigma value.

Response:
def get_rotation_angle_from_sigma(sigma, r_axis, lat_type='C', ratio=None):
        """
        Find all possible rotation angle for the given sigma value.

        Args:
            sigma (integer):
                    sigma value provided
            r_axis (list of three integers, e.g. u, v, w
                    or four integers, e.g. u, v, t, w for hex/rho system only):
                    the rotation axis of the grain boundary.
            lat_type ( one character):
                    'c' or 'C': cubic system
                     't' or 'T': tetragonal system
                     'o' or 'O': orthorhombic system
                     'h' or 'H': hexagonal system
                     'r' or 'R': rhombohedral system
                     default to cubic system
            ratio (list of integers):
                    lattice axial ratio.
                    For cubic system, ratio is not needed.
                    For tetragonal system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv = c2/a2. If it is irrational, set it to none.
                    For orthorhombic system, ratio = [mu, lam, mv], list of three integers,
                    that is, mu:lam:mv = c2:b2:a2. If irrational for one axis, set it to None.
                    e.g. mu:lam:mv = c2,None,a2, means b2 is irrational.
                    For rhombohedral system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv is the ratio of (1+2*cos(alpha)/cos(alpha).
                    If irrational, set it to None.
                    For hexagonal system, ratio = [mu, mv], list of two integers,
                    that is, mu/mv = c2/a2. If it is irrational, set it to none.

        Returns:
            rotation_angles corresponding to the provided sigma value.
            If the sigma value is not correct, return the rotation angle corresponding
            to the correct possible sigma value right smaller than the wrong sigma value provided.
        """
        if lat_type.lower() == 'c':
            logger.info('Make sure this is for cubic system')
            sigma_dict = GrainBoundaryGenerator.enum_sigma_cubic(cutoff=sigma, r_axis=r_axis)
        elif lat_type.lower() == 't':
            logger.info('Make sure this is for tetragonal system')
            if ratio is None:
                logger.info('Make sure this is for irrational c2/a2 ratio')
            elif len(ratio) != 2:
                raise RuntimeError('Tetragonal system needs correct c2/a2 ratio')
            sigma_dict = GrainBoundaryGenerator.enum_sigma_tet(cutoff=sigma, r_axis=r_axis, c2_a2_ratio=ratio)
        elif lat_type.lower() == 'o':
            logger.info('Make sure this is for orthorhombic system')
            if len(ratio) != 3:
                raise RuntimeError('Orthorhombic system needs correct c2:b2:a2 ratio')
            sigma_dict = GrainBoundaryGenerator.enum_sigma_ort(cutoff=sigma, r_axis=r_axis, c2_b2_a2_ratio=ratio)
        elif lat_type.lower() == 'h':
            logger.info('Make sure this is for hexagonal system')
            if ratio is None:
                logger.info('Make sure this is for irrational c2/a2 ratio')
            elif len(ratio) != 2:
                raise RuntimeError('Hexagonal system needs correct c2/a2 ratio')
            sigma_dict = GrainBoundaryGenerator.enum_sigma_hex(cutoff=sigma, r_axis=r_axis, c2_a2_ratio=ratio)
        elif lat_type.lower() == 'r':
            logger.info('Make sure this is for rhombohedral system')
            if ratio is None:
                logger.info('Make sure this is for irrational (1+2*cos(alpha)/cos(alpha) ratio')
            elif len(ratio) != 2:
                raise RuntimeError('Rhombohedral system needs correct '
                                   '(1+2*cos(alpha)/cos(alpha) ratio')
            sigma_dict = GrainBoundaryGenerator.enum_sigma_rho(cutoff=sigma, r_axis=r_axis, ratio_alpha=ratio)
        else:
            raise RuntimeError('Lattice type not implemented')

        sigmas = list(sigma_dict.keys())
        if not sigmas:
            raise RuntimeError('This is a wriong sigma value, and no sigma exists smaller than this value.')
        if sigma in sigmas:
            rotation_angles = sigma_dict[sigma]
        else:
            sigmas.sort()
            warnings.warn("This is not the possible sigma value according to the rotation axis!"
                          "The nearest neighbor sigma and its corresponding angle are returned")
            rotation_angles = sigma_dict[sigmas[-1]]
        rotation_angles.sort()
        return rotation_angles


Instruction:
Make a summary of the following Python 3 code
def slab_from_csl(csl, surface, normal, trans_cry, max_search=20, quick_gen=False):

        # set the transform matrix in real space
        trans = trans_cry
        # transform matrix in reciprocal space
        ctrans = np.linalg.inv(trans.T)

        t_matrix = csl.copy()
        # vectors constructed from csl that perpendicular to surface
        ab_vector = []
        # obtain the miller index of surface in terms of csl.
        miller = np.matmul(surface, csl.T)
        if reduce(gcd, miller) != 1:
            miller = [int(round(x / reduce(gcd, miller))) for x in miller]
        miller_nonzero = []
        # quickly generate a supercell, normal is not work in this way
        if quick_gen:
            scale_factor = []
            eye = np.eye(3, dtype=np.int)
            for i, j in enumerate(miller):
                if j == 0:
                    scale_factor.append(eye[i])
                else:
                    miller_nonzero.append(i)
            if len(scale_factor) < 2:
                index_len = len(miller_nonzero)
                for i in range(index_len):
                    for j in range(i + 1, index_len):
                        lcm_miller = lcm(miller[miller_nonzero[i]], miller[miller_nonzero[j]])
                        l = [0, 0, 0]
                        l[miller_nonzero[i]] = -int(round(lcm_miller / miller[miller_nonzero[i]]))
                        l[miller_nonzero[j]] = int(round(lcm_miller / miller[miller_nonzero[j]]))
                        scale_factor.append(l)
                        if len(scale_factor) == 2:
                            break
            t_matrix[0] = np.array(np.dot(scale_factor[0], csl))
            t_matrix[1] = np.array(np.dot(scale_factor[1], csl))
            t_matrix[2] = csl[miller_nonzero[0]]
            if abs(np.linalg.det(t_matrix)) > 1000:
                warnings.warn('Too large matrix. Suggest to use quick_gen=False')
            return t_matrix

        for i, j in enumerate(miller):
            if j == 0:
                ab_vector.append(csl[i])
            else:
                c_index = i
                miller_nonzero.append(j)

        if len(miller_nonzero) > 1:
            t_matrix[2] = csl[c_index]
            index_len = len(miller_nonzero)
            lcm_miller = []
            for i in range(index_len):
                for j in range(i + 1, index_len):
                    com_gcd = gcd(miller_nonzero[i], miller_nonzero[j])
                    mil1 = int(round(miller_nonzero[i] / com_gcd))
                    mil2 = int(round(miller_nonzero[j] / com_gcd))
                    lcm_miller.append(max(abs(mil1), abs(mil2)))
            lcm_sorted = sorted(lcm_miller)
            if index_len == 2:
                max_j = lcm_sorted[0]
            else:
                max_j = lcm_sorted[1]
        else:
            if not normal:
                t_matrix[0] = ab_vector[0]
                t_matrix[1] = ab_vector[1]
                t_matrix[2] = csl[c_index]
                return t_matrix
            else:
                max_j = abs(miller_nonzero[0])
        if max_j > max_search:
            max_j = max_search
        # area of a, b vectors
        area = None
        # length of c vector
        c_norm = np.linalg.norm(np.matmul(t_matrix[2], trans))
        # c vector length along the direction perpendicular to surface
        c_length = np.abs(np.dot(t_matrix[2], surface))
        # check if the init c vector perpendicular to the surface
        if normal:
            c_cross = np.cross(np.matmul(t_matrix[2], trans), np.matmul(surface, ctrans))
            if np.linalg.norm(c_cross) < 1.e-8:
                normal_init = True
            else:
                normal_init = False

        j = np.arange(0, max_j + 1)
        combination = []
        for i in itertools.product(j, repeat=3):
            if sum(abs(np.array(i))) != 0:
                combination.append(list(i))
            if len(np.nonzero(i)[0]) == 3:
                for i1 in range(3):
                    new_i = list(i).copy()
                    new_i[i1] = -1 * new_i[i1]
                    combination.append(new_i)
            elif len(np.nonzero(i)[0]) == 2:
                new_i = list(i).copy()
                new_i[np.nonzero(i)[0][0]] = -1 * new_i[np.nonzero(i)[0][0]]
                combination.append(new_i)
        for i in combination:
            if reduce(gcd, i) == 1:
                temp = np.dot(np.array(i), csl)
                if abs(np.dot(temp, surface) - 0) < 1.e-8:
                    ab_vector.append(temp)
                else:
                    # c vector length along the direction perpendicular to surface
                    c_len_temp = np.abs(np.dot(temp, surface))
                    # c vector length itself
                    c_norm_temp = np.linalg.norm(np.matmul(temp, trans))
                    if normal:
                        c_cross = np.cross(np.matmul(temp, trans), np.matmul(surface, ctrans))
                        if np.linalg.norm(c_cross) < 1.e-8:
                            if normal_init:
                                if c_norm_temp < c_norm:
                                    t_matrix[2] = temp
                                    c_norm = c_norm_temp
                            else:
                                c_norm = c_norm_temp
                                normal_init = True
                                t_matrix[2] = temp
                    else:
                        if c_len_temp < c_length or \
                                (abs(c_len_temp - c_length) < 1.e-8 and c_norm_temp < c_norm):
                            t_matrix[2] = temp
                            c_norm = c_norm_temp
                            c_length = c_len_temp

        if normal and (not normal_init):
            logger.info('Did not find the perpendicular c vector, increase max_j')
            while (not normal_init):
                if max_j == max_search:
                    warnings.warn('Cannot find the perpendicular c vector, please increase max_search')
                    break
                max_j = 3 * max_j
                if max_j > max_search:
                    max_j = max_search
                j = np.arange(0, max_j + 1)
                combination = []
                for i in itertools.product(j, repeat=3):
                    if sum(abs(np.array(i))) != 0:
                        combination.append(list(i))
                    if len(np.nonzero(i)[0]) == 3:
                        for i1 in range(3):
                            new_i = list(i).copy()
                            new_i[i1] = -1 * new_i[i1]
                            combination.append(new_i)
                    elif len(np.nonzero(i)[0]) == 2:
                        new_i = list(i).copy()
                        new_i[np.nonzero(i)[0][0]] = -1 * new_i[np.nonzero(i)[0][0]]
                        combination.append(new_i)
                for i in combination:
                    if reduce(gcd, i) == 1:
                        temp = np.dot(np.array(i), csl)
                        if abs(np.dot(temp, surface) - 0) > 1.e-8:
                            c_cross = np.cross(np.matmul(temp, trans), np.matmul(surface, ctrans))
                            if np.linalg.norm(c_cross) < 1.e-8:
                                # c vetor length itself
                                c_norm_temp = np.linalg.norm(np.matmul(temp, trans))
                                if normal_init:
                                    if c_norm_temp < c_norm:
                                        t_matrix[2] = temp
                                        c_norm = c_norm_temp
                                else:
                                    c_norm = c_norm_temp
                                    normal_init = True
                                    t_matrix[2] = temp
                if normal_init:
                    logger.info('Found perpendicular c vector')

        # find the best a, b vectors with their formed area smallest and average norm of a,b smallest.
        for i in itertools.combinations(ab_vector, 2):
            area_temp = np.linalg.norm(np.cross(np.matmul(i[0], trans),
                                                np.matmul(i[1], trans)))
            if abs(area_temp - 0) > 1.e-8:
                ab_norm_temp = np.linalg.norm(np.matmul(i[0], trans)) + \
                               np.linalg.norm(np.matmul(i[1], trans))
                if area is None:
                    area = area_temp
                    ab_norm = ab_norm_temp
                    t_matrix[0] = i[0]
                    t_matrix[1] = i[1]
                elif area_temp < area:
                    t_matrix[0] = i[0]
                    t_matrix[1] = i[1]
                    area = area_temp
                    ab_norm = ab_norm_temp
                elif abs(area - area_temp) < 1.e-8 and ab_norm_temp < ab_norm:
                    t_matrix[0] = i[0]
                    t_matrix[1] = i[1]
                    area = area_temp
                    ab_norm = ab_norm_temp

        # make sure we have a left-handed crystallographic system
        if np.linalg.det(np.matmul(t_matrix, trans)) < 0:
            t_matrix *= -1

        if normal and abs(np.linalg.det(t_matrix)) > 1000:
            warnings.warn('Too large matrix. Suggest to use Normal=False')
        return t_matrix

Response: This function creates a slab from a crystal system and a surface and a normal vector.


Instruction:
Can you implement a function in Python 3 that
reduces integer array mat by linear combination of determinant and rotation matrix r_matrix.

Response:
def reduce_mat(mat, mag, r_matrix):
        """
        Reduce integer array mat's determinant mag times by linear combination
        of its row vectors, so that the new array after rotation (r_matrix) is
        still an integer array

        Args:
            mat (3 by 3 array): input matrix
            mag (integer): reduce times for the determinant
            r_matrix (3 by 3 array): rotation matrix
        Return:
            the reduced integer array
        """
        max_j = abs(int(round(np.linalg.det(mat) / mag)))
        reduced = False
        for h in range(3):
            k = h + 1 if h + 1 < 3 else abs(2 - h)
            l = h + 2 if h + 2 < 3 else abs(1 - h)
            j = np.arange(-max_j, max_j + 1)
            for j1, j2 in itertools.product(j, repeat=2):
                temp = mat[h] + j1 * mat[k] + j2 * mat[l]
                if all([np.round(x, 5).is_integer() for x in list(temp / mag)]):
                    mat_copy = mat.copy()
                    mat_copy[h] = np.array([int(round(ele / mag)) for ele in temp])
                    new_mat = np.dot(mat_copy, np.linalg.inv(r_matrix.T))
                    if all([np.round(x, 5).is_integer() for x in list(np.ravel(new_mat))]):
                        reduced = True
                        mat[h] = np.array([int(round(ele / mag)) for ele in temp])
                        break
            if reduced:
                break

        if not reduced:
            warnings.warn("Matrix reduction not performed, may lead to non-primitive gb cell.")
        return mat


Instruction:
Here you have a function in Python 3, explain what it does
def vec_to_surface(vec):
        miller = [None] * 3
        index = []
        for i, value in enumerate(vec):
            if abs(value) < 1.e-8:
                miller[i] = 0
            else:
                index.append(i)
        if len(index) == 1:
            miller[index[0]] = 1
        else:
            min_index = np.argmin([i for i in vec if i != 0])
            true_index = index[min_index]
            index.pop(min_index)
            frac = []
            for i, value in enumerate(index):
                frac.append(Fraction(vec[value] / vec[true_index]).limit_denominator(100))
            if len(index) == 1:
                miller[true_index] = frac[0].denominator
                miller[index[0]] = frac[0].numerator
            else:
                com_lcm = lcm(frac[0].denominator, frac[1].denominator)
                miller[true_index] = com_lcm
                miller[index[0]] = frac[0].numerator * int(round((com_lcm / frac[0].denominator)))
                miller[index[1]] = frac[1].numerator * int(round((com_lcm / frac[1].denominator)))
        return miller

Response: Transform a vector to a surface miller index with integers.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def add_spectrum(self, label, spectrum, color=None):
        self._spectra[label] = spectrum
        self.colors.append(
            color or
            self.colors_cycle[len(self._spectra) % len(self.colors_cycle)])

Response: Adds a Spectrum to the internal list of available colors.


Instruction:
Given the following Python 3 function, write the documentation
def add_spectra(self, spectra_dict, key_sort_func=None):
        if key_sort_func:
            keys = sorted(spectra_dict.keys(), key=key_sort_func)
        else:
            keys = spectra_dict.keys()
        for label in keys:
            self.add_spectra(label, spectra_dict[label])

Response: Adds a dictionary of doses to the set of doses.


Instruction:
Explain what the following Python 3 code does
def get_plot(self, xlim=None, ylim=None):

        plt = pretty_plot(12, 8)
        base = 0.0
        i = 0
        for key, sp in self._spectra.items():
            if not self.stack:
                plt.plot(sp.x, sp.y + self.yshift * i, color=self.colors[i],
                         label=str(key), linewidth=3)
            else:
                plt.fill_between(sp.x, base, sp.y + self.yshift * i,
                                 color=self.colors[i],
                                 label=str(key), linewidth=3)
                base = sp.y + base
            plt.xlabel(sp.XLABEL)
            plt.ylabel(sp.YLABEL)
            i += 1

        if xlim:
            plt.xlim(xlim)
        if ylim:
            plt.ylim(ylim)

        plt.legend()
        leg = plt.gca().get_legend()
        ltext = leg.get_texts()  # all the text.Text instance in the legend
        plt.setp(ltext, fontsize=30)
        plt.tight_layout()
        return plt

Response: Returns a matplotlib plot showing the DOS.


Instruction:
Write a Python 3 script for
saving matplotlib plot to a file.

Response:
def save_plot(self, filename, img_format="eps", **kwargs):
        """
        Save matplotlib plot to a file.

        Args:
            filename: Filename to write to.
            img_format: Image format to use. Defaults to EPS.
        """
        plt = self.get_plot(**kwargs)
        plt.savefig(filename, format=img_format)


Instruction:
How would you explain what the following Python 3 function does
def apply_transformation(self, structure, return_ranked_list=False):
        sp = get_el_sp(self.specie_to_remove)
        specie_indices = [i for i in range(len(structure))
                          if structure[i].species ==
                          Composition({sp: 1})]
        trans = PartialRemoveSitesTransformation([specie_indices],
                                                 [self.fraction_to_remove],
                                                 algo=self.algo)
        return trans.apply_transformation(structure, return_ranked_list)

Response: Applies the transformation to the structure.


Instruction:
Explain what the following Python 3 code does
def apply_transformation(self, structure):
        sga = SpacegroupAnalyzer(structure, symprec=self.symprec,
                                 angle_tolerance=self.angle_tolerance)
        return sga.get_conventional_standard_structure(international_monoclinic=self.international_monoclinic)

Response: Returns most primitive cell for structure.


Instruction:
Implement a Python 3 function for
applying the transformation to a structure.

Response:
def apply_transformation(self, structure):
        """
        Discretizes the site occupancies in the structure.

        Args:
            structure: disordered Structure to discretize occupancies

        Returns:
            A new disordered Structure with occupancies discretized
        """
        if structure.is_ordered:
            return structure

        species = [dict(sp) for sp in structure.species_and_occu]

        for sp in species:
            for k, v in sp.items():
                old_occ = sp[k]
                new_occ = float(
                    Fraction(old_occ).limit_denominator(self.max_denominator))
                if self.fix_denominator:
                    new_occ = around(old_occ*self.max_denominator)\
                        / self.max_denominator
                if round(abs(old_occ - new_occ), 6) > self.tol:
                    raise RuntimeError(
                        "Cannot discretize structure within tolerance!")
                sp[k] = new_occ

        return Structure(structure.lattice, species, structure.frac_coords)


Instruction:
Write a Python 3 script for
returning a copy of the structure with the parameters scaled to the same degree as the relaxed_structure.

Response:
def apply_transformation(self, structure):
        """
        Returns a copy of structure with lattice parameters
        and sites scaled to the same degree as the relaxed_structure.

        Arg:
            structure (Structure): A structurally similar structure in
                regards to crystal and site positions.
        """

        if self.species_map == None:
            match = StructureMatcher()
            s_map = \
                match.get_best_electronegativity_anonymous_mapping(self.unrelaxed_structure,
                                                                   structure)
        else:
            s_map = self.species_map

        params = list(structure.lattice.abc)
        params.extend(structure.lattice.angles)
        new_lattice = Lattice.from_parameters(*[p*self.params_percent_change[i] \
                                                for i, p in enumerate(params)])
        species, frac_coords = [], []
        for site in self.relaxed_structure:
            species.append(s_map[site.specie])
            frac_coords.append(site.frac_coords)

        return Structure(new_lattice, species, frac_coords)


Instruction:
Can you implement a function in Python 3 that
returns a dict representation of the object.

Response:
def as_dict(self):
        """
        Makes LibxcFunc obey the general json interface used in pymatgen for
        easier serialization.
        """
        return {"name": self.name,
                "@module": self.__class__.__module__,
                "@class": self.__class__.__name__}


Instruction:
How would you explain what the following Python 3 function does
def lattice_2_lmpbox(lattice, origin=(0, 0, 0)):
    a, b, c = lattice.abc
    xlo, ylo, zlo = origin
    xhi = a + xlo
    m = lattice.matrix
    xy = np.dot(m[1], m[0] / a)
    yhi = np.sqrt(b ** 2 - xy ** 2) + ylo
    xz = np.dot(m[2], m[0] / a)
    yz = (np.dot(m[1], m[2]) - xy * xz) / (yhi - ylo)
    zhi = np.sqrt(c ** 2 - xz ** 2 - yz ** 2) + zlo
    tilt = None if lattice.is_orthogonal else [xy, xz, yz]
    rot_matrix = np.linalg.solve([[xhi - xlo, 0, 0],
                                  [xy, yhi - ylo, 0],
                                  [xz, yz, zhi - zlo]], m)
    bounds = [[xlo, xhi], [ylo, yhi], [zlo, zhi]]
    symmop = SymmOp.from_rotation_and_translation(rot_matrix, origin)
    return LammpsBox(bounds, tilt), symmop

Response:
Converts a Lattice object to LammpsBox and calculates the symmetry
            simulation box used.


Instruction:
Create a Python 3 function to
convert a structure to a LammpsData object with no force field parameters and topologies.

Response:
def structure_2_lmpdata(structure, ff_elements=None, atom_style="charge"):
    """
    Converts a structure to a LammpsData object with no force field
    parameters and topologies.

    Args:
        structure (Structure): Input structure.
        ff_elements ([str]): List of strings of elements that must be
            present due to force field settings but not necessarily in
            the structure. Default to None.
        atom_style (str): Choose between "atomic" (neutral) and
            "charge" (charged). Default to "charge".

    Returns:
        LammpsData

    """
    s = structure.get_sorted_structure()

    a, b, c = s.lattice.abc
    m = s.lattice.matrix
    xhi = a
    xy = np.dot(m[1], m[0] / xhi)
    yhi = np.sqrt(b ** 2 - xy ** 2)
    xz = np.dot(m[2], m[0] / xhi)
    yz = (np.dot(m[1], m[2]) - xy * xz) / yhi
    zhi = np.sqrt(c ** 2 - xz ** 2 - yz ** 2)
    box_bounds = [[0.0, xhi], [0.0, yhi], [0.0, zhi]]
    box_tilt = [xy, xz, yz]
    box_tilt = None if not any(box_tilt) else box_tilt
    box = LammpsBox(box_bounds, box_tilt)
    new_latt = Lattice([[xhi, 0, 0], [xy, yhi, 0], [xz, yz, zhi]])
    s.lattice = new_latt

    symbols = list(s.symbol_set)
    if ff_elements:
        symbols.extend(ff_elements)
    elements = sorted(Element(el) for el in set(symbols))
    mass_info = [tuple([i.symbol] * 2) for i in elements]
    ff = ForceField(mass_info)
    topo = Topology(s)
    return LammpsData.from_ff_and_topologies(box=box, ff=ff, topologies=[topo],
                                             atom_style=atom_style)