Exemple #1
0
def calc_bond_angle(c_octa):
    """
    Calculate 12 cis and 3 trans unique angles in octahedral structure.

    Parameters
    ----------
    c_octa : array or list
        Atomic coordinates of octahedral structure.

    Returns
    -------
    cis_angle : list
        List of 12 cis angles.
    trans_angle : list
        List of 3 trans angles.

    Examples
    --------
    >>> coord
    [[2.298354000, 5.161785000, 7.971898000],  # <- Metal atom
     [1.885657000, 4.804777000, 6.183726000],
     [1.747515000, 6.960963000, 7.932784000],
     [4.094380000, 5.807257000, 7.588689000],
     [0.539005000, 4.482809000, 8.460004000],
     [2.812425000, 3.266553000, 8.131637000],
     [2.886404000, 5.392925000, 9.848966000]]
    >>> cis, trans = calc_bond_angle(coord)
    >>> cis
    [82.75749025175044, 83.11074412601039, 87.07433386260743,
     87.217426970301, 87.59010057569893, 88.49485029114034,
     89.71529920540053, 94.09217259687905, 94.2481644447923,
     94.51379613736219, 95.40490801797102, 95.62773246517462]
    >>> trans
    [173.8820603662232, 176.07966588060893, 176.58468599461276]

    """
    if type(c_octa) == np.ndarray:
        pass
    else:
        c_octa = np.asarray(c_octa)

    all_angle = []
    for i in range(1, 7):
        for j in range(i + 1, 7):
            vec1 = c_octa[i] - c_octa[0]
            vec2 = c_octa[j] - c_octa[0]
            angle = linear.angle_btw_vectors(vec1, vec2)
            all_angle.append(angle)

    # Sort the angle from the lowest to the highest
    sorted_angle = sorted(all_angle)
    cis_angle = [sorted_angle[i] for i in range(12)]
    trans_angle = [sorted_angle[i] for i in range(12, 15)]

    return cis_angle, trans_angle
    def add_coord(self, atom, coord):
        """
        Add atomic symbols and coordinates to box.

        Parameters
        ----------
        atom : array_like
            Atomic labels of full complex.
        coord : array_like
            Atomic coordinates of full complex.

        """
        self.box.insert(tk.END, "Bond distance (Å)")

        # Bond distance
        for i in range(len(coord)):
            for j in range(i + 1, len(coord)):
                dist = distance.euclidean(coord[i], coord[j])

                if i == 0:
                    texts = f"{atom[i]}-{atom[j]}{j}\t\t{dist:10.6f}"
                else:
                    texts = f"{atom[i]}{i}-{atom[j]}{j}\t\t{dist:10.6f}"

                self.box.insert(tk.END, "\n" + texts)

        self.box.insert(tk.END, "\n\nBond angle (°)")

        # Bond angle.
        for i in range(len(coord)):
            for j in range(i + 1, len(coord)):
                for k in range(j + 1, 7):
                    vec1 = coord[i] - coord[j]
                    vec2 = coord[k] - coord[j]
                    angle = linear.angle_btw_vectors(vec1, vec2)

                    if i == 0:
                        texts = f"{atom[k]}{k}-{atom[i]}-{atom[j]}{j}\t\t{angle:10.6f}"
                    else:
                        texts = (
                            f"{atom[k]}{k}-{atom[i]}{i}-{atom[j]}{j}\t\t{angle:10.6f}"
                        )

                    self.box.insert(tk.END, "\n" + texts)

        self.box.insert(tk.END, "\n\n=================================\n\n")
Exemple #3
0
    def calc_bond_angle(self):
        """
        Calculate 12 cis and 3 trans unique angles in octahedral structure.

        See Also
        --------
        calc_sigma : Calculate Sigma parameter.

        """
        all_angle = []
        for i in range(1, 7):
            for j in range(i + 1, 7):
                vec1 = self.coord[i] - self.coord[0]
                vec2 = self.coord[j] - self.coord[0]
                angle = linear.angle_btw_vectors(vec1, vec2)
                all_angle.append(angle)

        # Sort the angle from the lowest to the highest
        sorted_angle = sorted(all_angle)
        self.cis_angle = [sorted_angle[i] for i in range(12)]
        self.trans_angle = [sorted_angle[i] for i in range(12, 15)]
Exemple #4
0
def param_octa(aco):
    """
    Show structural parameters of selected octahedral structure.

    Parameters
    ----------
    aco : list
        Atomic labels and coordinates of octahedral structure.

    Returns
    -------
    None : None

    """
    master = tk.Tk()
    master.title("Results")
    master.geometry("380x530")
    master.option_add("*Font", "Arial 10")
    frame = tk.Frame(master)
    frame.grid()
    lbl = tk.Label(frame, text="Structural parameters of octahedral structure")
    lbl.grid(row=0, pady="5", padx="5", sticky=tk.W)
    box = tkscrolled.ScrolledText(frame,
                                  wrap="word",
                                  width="50",
                                  height="30",
                                  undo="True")
    box.grid(row=1, pady="5", padx="5")

    for n in range(len(aco)):
        if n > 0:  # separator between files
            box.insert(tk.END, "\n\n=================================\n\n")

        box.insert(tk.INSERT, f"File : {aco[n][0]}\n")
        box.insert(tk.END, f"Metal: {aco[n][1]}\n")
        box.insert(tk.END, "Bond distance (Å)")

        for i in range(7):
            for j in range(i + 1, 7):
                distance = linear.euclidean_dist(aco[n][3][i], aco[n][3][j])

                if i == 0:
                    texts = f"{aco[n][2][i]}-{aco[n][2][j]}{j} {distance:10.6f}"

                else:
                    texts = f"{aco[n][2][i]}{i}-{aco[n][2][j]}{j} {distance:10.6f}"

                box.insert(tk.END, "\n" + texts)

        box.insert(tk.END, "\n\nBond angle (°)")

        for i in range(7):
            for j in range(i + 1, 7):
                for k in range(j + 1, 7):
                    vec1 = aco[n][3][i] - aco[n][3][j]
                    vec2 = aco[n][3][k] - aco[n][3][j]
                    angle = linear.angle_btw_vectors(vec1, vec2)

                    if i == 0:
                        texts = f"{aco[n][2][k]}{k}-{aco[n][2][i]}-{aco[n][2][j]}{j} {angle:10.6f}"

                    else:
                        texts = f"{aco[n][2][k]}{k}-{aco[n][2][i]}{i}-{aco[n][2][j]}{j} {angle:10.6f}"

                    box.insert(tk.END, "\n" + texts)

        box.insert(tk.END, "\n")
Exemple #5
0
def param_complex(acf):
    """
    Show structural parameters of the complex.

    Parameters
    ----------
    acf : list
        Atomic labels and coordinates of full complex.

    Returns
    -------
    None : None

    """
    master = tk.Tk()
    master.title("Results")
    master.geometry("380x530")
    master.option_add("*Font", "Arial 10")
    frame = tk.Frame(master)
    frame.grid()
    lbl = tk.Label(frame, text="Structural parameters of octahedral structure")
    lbl.grid(row=0, pady="5", padx="5", sticky=tk.W)
    box = tkscrolled.ScrolledText(frame,
                                  wrap="word",
                                  width="50",
                                  height="30",
                                  undo="True")
    box.grid(row=1, pady="5", padx="5")
    box.insert(tk.INSERT, "Bond distance (Å)")

    fal, fcl = acf[0]
    for i in range(len(fcl)):
        for j in range(i + 1, len(fcl)):
            distance = linear.euclidean_dist(fcl[i], fcl[j])

            if i == 0:
                texts = f"{fal[i]}-{fal[j]}{j} {distance:10.6f}"

            else:
                texts = f"{fal[i]}{i}-{fal[j]}{j} {distance:10.6f}"

            box.insert(tk.END, "\n" + texts)

    box.insert(tk.END, "\n\nBond angle (°)")

    for i in range(len(fcl)):
        for j in range(i + 1, len(fcl)):
            for k in range(j + 1, len(fcl)):
                vec1 = fcl[i] - fcl[j]
                vec2 = fcl[k] - fcl[j]
                angle = linear.angle_btw_vectors(vec1, vec2)

                if i == 0:
                    texts = f"{fal[k]}{k}-{fal[i]}-{fal[j]}{j} {angle:10.6f}"

                else:
                    texts = f"{fal[k]}{k}-{fal[i]}{i}-{fal[j]}{j} {angle:10.6f}"

                box.insert(tk.END, "\n" + texts)

    box.insert(tk.END, "\n")
Exemple #6
0
    def calc_theta(self):
        """
        Calculate Theta parameter and value in degree.

        See Also
        --------
        calc_theta_min :
            Calculate minimum Theta parameter.
        calc_theta_max :
            Calculate maximum Theta parameter.
        octadist.src.linear.angle_btw_vectors :
            Calculate cosine angle between two vectors.
        octadist.src.linear.angle_sign
            Calculate cosine angle between two vectors sensitive to CW/CCW direction.
        octadist.src.plane.find_eq_of_plane :
            Find the equation of the plane.
        octadist.src.projection.project_atom_onto_plane :
            Orthogonal projection of point onto the plane.

        References
        ----------
        .. [4] M. Marchivie, P. Guionneau, J.-F. Létard, D. Chasseau.
            Photo‐induced spin‐transition: the role of the iron(II)
            environment distortion. Acta Crystal-logr. Sect. B Struct.
            Sci. 2005, 61, 25.

        """
        # Get refined atomic coordinates
        coord_metal, coord_lig = self.determine_faces()

        # loop over 8 faces
        for r in range(8):
            a, b, c, d = plane.find_eq_of_plane(
                coord_lig[0], coord_lig[1], coord_lig[2]
            )
            self.eq_of_plane.append([a, b, c, d])

            # Project metal and other three ligand atom onto the plane
            projected_m = projection.project_atom_onto_plane(coord_metal, a, b, c, d)
            projected_lig4 = projection.project_atom_onto_plane(
                coord_lig[3], a, b, c, d
            )
            projected_lig5 = projection.project_atom_onto_plane(
                coord_lig[4], a, b, c, d
            )
            projected_lig6 = projection.project_atom_onto_plane(
                coord_lig[5], a, b, c, d
            )

            # Find the vectors between atoms that are on the same plane
            # These vectors will be used to calculate Theta afterward.
            vector_theta = np.array(
                [
                    coord_lig[0] - projected_m,
                    coord_lig[1] - projected_m,
                    coord_lig[2] - projected_m,
                    projected_lig4 - projected_m,
                    projected_lig5 - projected_m,
                    projected_lig6 - projected_m,
                ]
            )

            # Check if the direction is CW or CCW
            a12 = linear.angle_btw_vectors(vector_theta[0], vector_theta[1])
            a13 = linear.angle_btw_vectors(vector_theta[0], vector_theta[2])

            # If angle of interest is smaller than its neighbor,
            # define it as CW direction, if not, it will be CCW instead.
            if a12 < a13:
                direction = np.cross(vector_theta[0], vector_theta[1])
            else:
                direction = np.cross(vector_theta[2], vector_theta[0])

            # Calculate individual theta angle
            theta1 = linear.angle_sign(vector_theta[0], vector_theta[3], direction)
            theta2 = linear.angle_sign(vector_theta[3], vector_theta[1], direction)
            theta3 = linear.angle_sign(vector_theta[1], vector_theta[4], direction)
            theta4 = linear.angle_sign(vector_theta[4], vector_theta[2], direction)
            theta5 = linear.angle_sign(vector_theta[2], vector_theta[5], direction)
            theta6 = linear.angle_sign(vector_theta[5], vector_theta[0], direction)

            indi_theta = np.array([theta1, theta2, theta3, theta4, theta5, theta6])

            self.eight_theta.append(sum(abs(indi_theta - 60)))

            # Use deep copy so as to avoid pass by reference
            tmp = coord_lig[1].copy()
            coord_lig[1] = coord_lig[3].copy()
            coord_lig[3] = coord_lig[5].copy()
            coord_lig[5] = coord_lig[2].copy()
            coord_lig[2] = tmp.copy()

            # If 3rd round, permutation face will be switched
            # from N1N2N3
            # to N1N4N2,
            # to N1N6N4,
            # to N1N3N6, and then back to N1N2N3
            if r == 3:
                coord_lig[[0, 4]] = coord_lig[[4, 0]]
                coord_lig[[1, 5]] = coord_lig[[5, 1]]
                coord_lig[[2, 3]] = coord_lig[[3, 2]]

        self.theta = sum(self.eight_theta) / 2
Exemple #7
0
    def determine_faces(self):
        """
        Refine the order of ligand atoms in order to find the plane
        for projection.

        Returns
        -------
        coord_metal : nd.array
            Coordinate of metal atom.
        coord_lig : nd.array
            Coordinate of ligand atoms.

        See Also
        --------
        calc_theta :
            Calculate mean Theta parameter

        Examples
        --------
        >>> bef = np.array([[4.0674, 7.2040, 13.6117]
                            [4.3033, 7.3750, 11.7292]
                            [3.8326, 6.9715, 15.4926]
                            [5.8822, 6.4461, 13.4312]
                            [3.3002, 5.3828, 13.6316]
                            [4.8055, 8.9318, 14.2716]
                            [2.3184, 8.0165, 13.1152]
                            ])

        >>> metal, coord = self.determine_faces(bef)
        >>> metal
        [ 4.0674  7.204  13.6117]
        >>> coord_lig
        [[ 4.3033  7.375  11.7292]      # Front face
         [ 4.8055  8.9318 14.2716]      # Front face
         [ 5.8822  6.4461 13.4312]      # Front face
         [ 2.3184  8.0165 13.1152]      # Back face
         [ 3.8326  6.9715 15.4926]      # Back face
         [ 3.3002  5.3828 13.6316]]     # Back face

        """
        # Metal and ligand atoms
        coord_metal = self.coord[0]
        ligands = self.coord[1:]
        coord_lig = np.array([self.coord[i] for i in range(1, 7)])

        # Find vector from metal to ligand atoms
        metal_to_lig = coord_lig - coord_metal

        # Find maximum angle
        max_angle = self.trans_angle[0]

        # Identify which N is in line with ligand 1
        def_change = 6
        for n in range(6):
            test = linear.angle_btw_vectors(metal_to_lig[0], metal_to_lig[n])
            if test > (max_angle - 1):
                def_change = n

        test_max = 0
        new_change = 0
        for n in range(6):
            test = linear.angle_btw_vectors(metal_to_lig[0], metal_to_lig[n])
            if test > test_max:
                test_max = test
                new_change = n

        # Check if the structure is octahedron or not
        if def_change != new_change:
            self.non_octa = True
            def_change = new_change

        # Swap ligand
        # As ligands in 2-dimensions array, we use the following technique
        # to swap two lists of elements with avoiding passing reference value
        ligands[[4, def_change]] = ligands[[def_change, 4]]

        # Update vector from metal to ligand atoms
        metal_to_lig = ligands - coord_metal

        # Identify which N is in line with ligand 2
        for n in range(6):
            test = linear.angle_btw_vectors(metal_to_lig[1], metal_to_lig[n])
            if test > (max_angle - 1):
                def_change = n

        test_max = 0
        for n in range(6):
            test = linear.angle_btw_vectors(metal_to_lig[1], metal_to_lig[n])
            if test > test_max:
                test_max = test
                new_change = n

        if def_change != new_change:
            self.non_octa = True
            def_change = new_change

        # Swap ligand
        ligands[[5, def_change]] = ligands[[def_change, 5]]

        # Update vector from metal to ligand atoms
        metal_to_lig = ligands - coord_metal

        # Identify which N is in line with ligand 3
        for n in range(6):
            test = linear.angle_btw_vectors(metal_to_lig[2], metal_to_lig[n])
            if test > (max_angle - 1):
                def_change = n

        test_max = 0
        for n in range(6):
            test = linear.angle_btw_vectors(metal_to_lig[2], metal_to_lig[n])
            if test > test_max:
                test_max = test
                new_change = n

        if def_change != new_change:
            self.non_octa = True
            def_change = new_change

        # Swap ligand
        ligands[[3, def_change]] = ligands[[def_change, 3]]

        # New atom order
        coord_lig = np.array([ligands[i] for i in range(6)])

        return coord_metal, coord_lig
Exemple #8
0
def calc_theta(c_octa):
    """
    Calculate Theta parameter and value in degree.

          24
    Θ = sigma < 60 - angle_i >
         i=1

    where angle_i is an unique angle between two vectors of two twisting face.

    Parameters
    ----------
    c_octa : array or list
        Atomic coordinates of octahedral structure.

    Returns
    -------
    theta_mean : float
        Mean Theta value.

    References
    ----------
    M. Marchivie et al. Acta Crystal-logr. Sect. B Struct. Sci. 2005, 61, 25.

    Examples
    --------
    >>> coord
    [[2.298354000, 5.161785000, 7.971898000],  # <- Metal atom
     [1.885657000, 4.804777000, 6.183726000],
     [1.747515000, 6.960963000, 7.932784000],
     [4.094380000, 5.807257000, 7.588689000],
     [0.539005000, 4.482809000, 8.460004000],
     [2.812425000, 3.266553000, 8.131637000],
     [2.886404000, 5.392925000, 9.848966000]]
    >>> calc_theta(coord)
    122.68897277454599

    """
    if type(c_octa) == np.ndarray:
        pass
    else:
        c_octa = np.asarray(c_octa)

    ligands = list(c_octa[1:])

    TM = c_octa[0]
    N1 = c_octa[1]
    N2 = c_octa[2]
    N3 = c_octa[3]
    N4 = c_octa[4]
    N5 = c_octa[5]
    N6 = c_octa[6]

    # Vector from metal to ligand atom
    TMN1 = N1 - TM
    TMN2 = N2 - TM
    TMN3 = N3 - TM
    TMN4 = N4 - TM
    TMN5 = N5 - TM
    TMN6 = N6 - TM

    ligands_vec = [TMN1, TMN2, TMN3, TMN4, TMN5, TMN6]

    ###########################################
    # Determine the order of atoms in complex #
    ###########################################

    _, trans_angle = calc_bond_angle(c_octa)

    max_angle = trans_angle[0]

    # This loop is used to identify which N is in line with N1
    def_change = 6
    for n in range(6):
        test = linear.angle_btw_vectors(ligands_vec[0], ligands_vec[n])
        if test > (max_angle - 1):
            def_change = n

    test_max = 0
    new_change = 0
    for n in range(6):
        test = linear.angle_btw_vectors(ligands_vec[0], ligands_vec[n])
        if test > test_max:
            test_max = test
            new_change = n

    # geometry is used to identify the type of octahedral structure
    geometry = False
    if def_change != new_change:
        geometry = True
        def_change = new_change

    tp = ligands[4]
    ligands[4] = ligands[def_change]
    ligands[def_change] = tp

    N1 = ligands[0]
    N2 = ligands[1]
    N3 = ligands[2]
    N4 = ligands[3]
    N5 = ligands[4]
    N6 = ligands[5]

    TMN1 = N1 - TM
    TMN2 = N2 - TM
    TMN3 = N3 - TM
    TMN4 = N4 - TM
    TMN5 = N5 - TM
    TMN6 = N6 - TM

    ligands_vec = [TMN1, TMN2, TMN3, TMN4, TMN5, TMN6]

    # This loop is used to identify which N is in line with N2
    for n in range(6):
        test = linear.angle_btw_vectors(ligands_vec[1], ligands_vec[n])
        if test > (max_angle - 1):
            def_change = n

    # This loop is used to identify which N is in line with N2
    test_max = 0
    for n in range(6):
        test = linear.angle_btw_vectors(ligands_vec[1], ligands_vec[n])
        if test > test_max:
            test_max = test
            new_change = n

    if def_change != new_change:
        geometry = True
        def_change = new_change

    # Swapping the atom (n+1) just identified above with N6
    tp = ligands[5]
    ligands[5] = ligands[def_change]
    ligands[def_change] = tp

    # New atom order is stored into the N1 - N6 lists
    N1 = ligands[0]
    N2 = ligands[1]
    N3 = ligands[2]
    N4 = ligands[3]
    N5 = ligands[4]
    N6 = ligands[5]

    TMN1 = N1 - TM
    TMN2 = N2 - TM
    TMN3 = N3 - TM
    TMN4 = N4 - TM
    TMN5 = N5 - TM
    TMN6 = N6 - TM

    ligands_vec = [TMN1, TMN2, TMN3, TMN4, TMN5, TMN6]

    # This loop is used to identify which N is in line with N3
    for n in range(6):
        test = linear.angle_btw_vectors(ligands_vec[2], ligands_vec[n])
        if test > (max_angle - 1):
            def_change = n

    # This loop is used to identify which N is in line with N3
    test_max = 0
    for n in range(6):
        test = linear.angle_btw_vectors(ligands_vec[2], ligands_vec[n])
        if test > test_max:
            test_max = test
            new_change = n

    if def_change != new_change:
        geometry = True
        def_change = new_change

    # Swapping of the atom (n+1) just identified above with N4
    tp = ligands[3]
    ligands[3] = ligands[def_change]
    ligands[def_change] = tp

    # New atom order is stored into the N1 - N6 lists
    N1 = ligands[0]
    N2 = ligands[1]
    N3 = ligands[2]
    N4 = ligands[3]
    N5 = ligands[4]
    N6 = ligands[5]

    #####################################################
    # Calculate the Theta parameter ans its derivatives #
    #####################################################

    eqOfPlane = []
    indiTheta = []
    allTheta = []

    # loop over 8 faces
    for proj in range(8):
        a, b, c, d = octadist.src.plane.find_eq_of_plane(N1, N2, N3)
        eqOfPlane.append([a, b, c, d])

        # Project M, N4, N5, and N6 onto the plane defined by N1, N2, and N3
        TMP = projection.project_atom_onto_plane(TM, a, b, c, d)
        N4P = projection.project_atom_onto_plane(N4, a, b, c, d)
        N5P = projection.project_atom_onto_plane(N5, a, b, c, d)
        N6P = projection.project_atom_onto_plane(N6, a, b, c, d)

        VTh1 = N1 - TMP
        VTh2 = N2 - TMP
        VTh3 = N3 - TMP
        VTh4 = N4P - TMP
        VTh5 = N5P - TMP
        VTh6 = N6P - TMP

        a12 = linear.angle_btw_vectors(VTh1, VTh2)
        a13 = linear.angle_btw_vectors(VTh1, VTh3)
        if a12 < a13:
            direction = np.cross(VTh1, VTh2)
        else:
            direction = np.cross(VTh3, VTh1)

        theta1 = linear.angle_sign(VTh1, VTh4, direction)
        theta2 = linear.angle_sign(VTh4, VTh2, direction)
        theta3 = linear.angle_sign(VTh2, VTh5, direction)
        theta4 = linear.angle_sign(VTh5, VTh3, direction)
        theta5 = linear.angle_sign(VTh3, VTh6, direction)
        theta6 = linear.angle_sign(VTh6, VTh1, direction)

        indiTheta.append([theta1, theta2, theta3, theta4, theta5, theta6])

        sum_theta = sum(abs(indiTheta[proj][i] - 60) for i in range(6))

        allTheta.append(sum_theta)

        tp = N2
        N2 = N4
        N4 = N6
        N6 = N3
        N3 = tp

        # If the proj = 3, permutation face will be switched from N1N2N3
        # to N1N4N2, to N1N6N4, then to N1N3N6, and then back to N1N2N3
        if proj == 3:
            tp = N1
            N1 = N5
            N5 = tp
            tp = N2
            N2 = N6
            N6 = tp
            tp = N3
            N3 = N4
            N4 = tp

        # End of the loop that calculate the 8 projections.

    theta_mean = sum(allTheta[i] for i in range(8)) / 2

    # If geometry is True, the structure is non-octahedron
    if geometry:
        print("Non-octahedral structure detected!")

    return theta_mean