Ejemplo n.º 1
0
def get_symmetry(mol, already_oriented=False):
    '''
    Return a list of SymmOps for a molecule's point symmetry
    already_oriented: whether or not the principle axes of mol are already reoriented 
    '''
    pga = PointGroupAnalyzer(mol)
    #Handle linear molecules
    if '*' in pga.sch_symbol:
        if already_oriented == False:
            #Reorient the molecule
            oriented_mol, P = reoriented_molecule(mol)
            pga = PointGroupAnalyzer(oriented_mol)
        pg = pga.get_pointgroup()
        symm_m = []
        for op in pg:
            symm_m.append(op)
        #Add 12-fold  and reflections in place of ininitesimal rotation
        for axis in [[1, 0, 0], [0, 1, 0], [0, 0, 1]]:
            op = SymmOp.from_rotation_and_translation(aa2matrix(axis, pi / 6),
                                                      [0, 0, 0])
            if pga.is_valid_op(op):
                symm_m.append(op)
                #Any molecule with infinitesimal symmetry is linear;
                #Thus, it possess mirror symmetry for any axis perpendicular
                #To the rotational axis. pymatgen does not add this symmetry
                #for all linear molecules - for example, hydrogen
                if axis == [1, 0, 0]:
                    symm_m.append(SymmOp.from_xyz_string('x,-y,z'))
                    symm_m.append(SymmOp.from_xyz_string('x,y,-z'))
                elif axis == [0, 1, 0]:
                    symm_m.append(SymmOp.from_xyz_string('-x,y,z'))
                    symm_m.append(SymmOp.from_xyz_string('x,y,-z'))
                elif axis == [0, 0, 1]:
                    symm_m.append(SymmOp.from_xyz_string('-x,y,z'))
                    symm_m.append(SymmOp.from_xyz_string('x,-y,z'))
                #Generate a full list of SymmOps for the molecule's pointgroup
                symm_m = generate_full_symmops(symm_m, 1e-3)
                break
        #Reorient the SymmOps into mol's original frame
        if not already_oriented:
            new = []
            for op in symm_m:
                new.append(P.inverse * op * P)
            return new
        elif already_oriented:
            return symm_m
    #Handle nonlinear molecules
    else:
        pg = pga.get_pointgroup()
        symm_m = []
        for op in pg:
            symm_m.append(op)
        return symm_m
Ejemplo n.º 2
0
def orientation_in_wyckoff_position(
    mol,
    wyckoff_position,
    randomize=True,
    exact_orientation=False,
    already_oriented=False,
    allow_inversion=True,
    rtol=1e-2,
):
    """
    Tests if a molecule meets the symmetry requirements of a Wyckoff position,
    and returns the valid orientations.

    Args:
        mol: a Molecule object. Orientation is arbitrary
        wyckoff_position: a pyxtal.symmetry.Wyckoff_position object
        randomize: whether or not to apply a random rotation consistent with
            the symmetry requirements
        exact_orientation: whether to only check compatibility for the provided
            orientation of the molecule. Used within general case for checking.
            If True, this function only returns True or False
        already_oriented: whether or not to reorient the principle axes
            when calling get_symmetry. Setting to True can remove redundancy,
            but is not necessary
        allow_inversion: whether or not to allow chiral molecules to be
            inverted. Should only be True if the chemical and biological
            properties of the mirror image are known to be suitable for the
            desired application

    Returns:
        a list of operations.Orientation objects which can be applied to the
        molecule while allowing it to satisfy the symmetry requirements of the
        Wyckoff position. If no orientations are found, returns False.
    """
    # For single atoms, there are no constraints
    if len(mol) == 1:
        return [Orientation([[1, 0, 0], [0, 1, 0], [0, 0, 1]], degrees=2)]

    wyckoffs = wyckoff_position.ops
    w_symm = wyckoff_position.symmetry_m

    # Obtain the Wyckoff symmetry
    symm_w = w_symm[0]
    pga = PointGroupAnalyzer(mol)

    # Check exact orientation
    if exact_orientation is True:
        mo = deepcopy(mol)
        valid = True
        for op in symm_w:
            if not pga.is_valid_op(op):
                valid = False
        if valid is True:
            return True
        elif valid is False:
            return False

    # Obtain molecular symmetry, exact_orientation==False
    symm_m = get_symmetry(mol, already_oriented=already_oriented)
    # Store OperationAnalyzer objects for each molecular SymmOp
    chiral = True
    opa_m = []
    for op_m in symm_m:
        opa = OperationAnalyzer(op_m)
        opa_m.append(opa)
        if opa.type == "rotoinversion":
            chiral = False
        elif opa.type == "inversion":
            chiral = False

    # If molecule is chiral and allow_inversion is False,
    # check if WP breaks symmetry
    if chiral is True:
        if allow_inversion is False:
            for op in wyckoffs:
                if np.linalg.det(op.rotation_matrix) < 0:
                    printx(
                        "Warning: cannot place chiral molecule in spagegroup",
                        priority=2,
                    )
                    return False

    # Store OperationAnalyzer objects for each Wyckoff symmetry SymmOp
    opa_w = []
    for op_w in symm_w:
        opa_w.append(OperationAnalyzer(op_w))

    # Check for constraints from the Wyckoff symmetry...
    # If we find ANY two constraints (SymmOps with unique axes), the molecule's
    # point group MUST contain SymmOps which can be aligned to these particular
    # constraints. However, there may be multiple compatible orientations of the
    # molecule consistent with these constraints
    constraint1 = None
    constraint2 = None
    for i, op_w in enumerate(symm_w):
        if opa_w[i].axis is not None:
            constraint1 = opa_w[i]
            for j, op_w in enumerate(symm_w):
                if opa_w[j].axis is not None:
                    dot = np.dot(opa_w[i].axis, opa_w[j].axis)
                    if (not np.isclose(dot, 1, rtol=rtol)) and (not np.isclose(
                            dot, -1, rtol=rtol)):
                        constraint2 = opa_w[j]
                        break
            break
    # Indirectly store the angle between the constraint axes
    if constraint1 is not None and constraint2 is not None:
        dot_w = np.dot(constraint1.axis, constraint2.axis)
    # Generate 1st consistent molecular constraints
    constraints_m = []
    if constraint1 is not None:
        for i, opa1 in enumerate(opa_m):
            if opa1.is_conjugate(constraint1):
                constraints_m.append([opa1, []])
                # Generate 2nd constraint in opposite direction
                extra = deepcopy(opa1)
                extra.axis = [
                    opa1.axis[0] * -1, opa1.axis[1] * -1, opa1.axis[2] * -1
                ]
                constraints_m.append([extra, []])

    # Remove redundancy for the first constraints
    list_i = list(range(len(constraints_m)))
    list_j = list(range(len(constraints_m)))
    copy = deepcopy(constraints_m)
    for i, c1 in enumerate(copy):
        if i in list_i:
            for j, c2 in enumerate(copy):
                if i > j and j in list_j and j in list_i:
                    # Check if axes are colinear
                    if np.isclose(np.dot(c1[0].axis, c2[0].axis), 1,
                                  rtol=rtol):
                        list_i.remove(j)
                        list_j.remove(j)
                    # Check if axes are symmetrically equivalent
                    else:
                        cond1 = False
                        # cond2 = False
                        for opa in opa_m:
                            if opa.type == "rotation":
                                op = opa.op
                                if np.isclose(
                                        np.dot(op.operate(c1[0].axis),
                                               c2[0].axis),
                                        1,
                                        rtol=5 * rtol,
                                ):
                                    cond1 = True
                                    break
                        if cond1 is True:  # or cond2 is True:
                            list_i.remove(j)
                            list_j.remove(j)
    c_m = deepcopy(constraints_m)
    constraints_m = []
    for i in list_i:
        constraints_m.append(c_m[i])

    # Generate 2nd consistent molecular constraints
    valid = list(range(len(constraints_m)))
    if constraint2 is not None:
        for i, c in enumerate(constraints_m):
            opa1 = c[0]
            for j, opa2 in enumerate(opa_m):
                if opa2.is_conjugate(constraint2):
                    dot_m = np.dot(opa1.axis, opa2.axis)
                    # Ensure that the angles are equal
                    if abs(dot_m - dot_w) < 0.02 or abs(dot_m + dot_w) < 0.02:
                        constraints_m[i][1].append(opa2)
                        # Generate 2nd constraint in opposite direction
                        extra = deepcopy(opa2)
                        extra.axis = [
                            opa2.axis[0] * -1,
                            opa2.axis[1] * -1,
                            opa2.axis[2] * -1,
                        ]
                        constraints_m[i][1].append(extra)
            # If no consistent constraints are found, remove first constraint
            if constraints_m[i][1] == []:
                valid.remove(i)
    copy = deepcopy(constraints_m)
    constraints_m = []
    for i in valid:
        constraints_m.append(copy[i])

    # Generate orientations consistent with the possible constraints
    orientations = []
    # Loop over molecular constraint sets
    for c1 in constraints_m:
        v1 = c1[0].axis
        v2 = constraint1.axis
        T = rotate_vector(v1, v2)
        # If there is only one constraint
        if c1[1] == []:
            o = Orientation(T, degrees=1, axis=constraint1.axis)
            orientations.append(o)
        else:
            # Loop over second molecular constraints
            for opa in c1[1]:
                phi = angle(constraint1.axis, constraint2.axis)
                phi2 = angle(constraint1.axis, np.dot(T, opa.axis))
                if np.isclose(phi, phi2, rtol=rtol):
                    r = np.sin(phi)
                    c = np.linalg.norm(np.dot(T, opa.axis) - constraint2.axis)
                    theta = np.arccos(1 - (c**2) / (2 * (r**2)))
                    # R = aa2matrix(constraint1.axis, theta)
                    R = Rotation.from_rotvec(theta *
                                             constraint1.axis).as_matrix()
                    T2 = np.dot(R, T)
                    a = angle(np.dot(T2, opa.axis), constraint2.axis)
                    if not np.isclose(a, 0, rtol=rtol):
                        T2 = np.dot(np.linalg.inv(R), T)
                    o = Orientation(T2, degrees=0)
                    orientations.append(o)

    # Ensure the identity orientation is checked if no constraints are found
    if constraints_m == []:
        o = Orientation(np.identity(3), degrees=2)
        orientations.append(o)

    # Remove redundancy from orientations
    list_i = list(range(len(orientations)))
    list_j = list(range(len(orientations)))
    for i, o1 in enumerate(orientations):
        if i in list_i:
            for j, o2 in enumerate(orientations):
                if i > j and j in list_j and j in list_i:
                    # m1 = o1.get_matrix(angle=0)
                    # m2 = o2.get_matrix(angle=0)
                    m1 = o1.matrix
                    m2 = o2.matrix
                    new_op = SymmOp.from_rotation_and_translation(
                        np.dot(m2, np.linalg.inv(m1)), [0, 0, 0])
                    P = SymmOp.from_rotation_and_translation(
                        np.linalg.inv(m1), [0, 0, 0])
                    old_op = P * new_op * P.inverse
                    if pga.is_valid_op(old_op):
                        list_i.remove(j)
                        list_j.remove(j)
    #copies = deepcopy(orientations)
    orientations_new = []
    for i in list_i:
        orientations_new.append(orientations[i])

    #Check each of the found orientations for consistency with the Wyckoff pos.
    #If consistent, put into an array of valid orientations
    allowed = []
    for o in orientations_new:
        if randomize is True:
            op = o.get_op()
        elif randomize is False:
            op = o.get_op()  #do not change
        mo = deepcopy(mol)
        mo.apply_operation(op)
        if orientation_in_wyckoff_position(mo,
                                           wyckoff_position,
                                           exact_orientation=True,
                                           randomize=False,
                                           allow_inversion=allow_inversion):
            allowed.append(o)
    if allowed == []:
        return False
    else:
        return allowed
Ejemplo n.º 3
0
def get_symmetry(mol, already_oriented=False):
    """
    Return a molecule's point symmetry.
    Note: for linear molecules, infinitessimal rotations are treated as 6-fold
    rotations, which works for 3d and 2d point groups.

    Args:
        mol: a Molecule object
        already_oriented: whether or not the principle axes of mol are already
            reoriented. Can save time if True, but is not required.

    Returns:
        a list of SymmOp objects which leave the molecule unchanged when applied
    """
    # For single atoms, we cannot represent the point group using a list of operations
    if len(mol) == 1:
        return []
    pga = PointGroupAnalyzer(mol)
    # Handle linear molecules
    if "*" in pga.sch_symbol:
        if not already_oriented:
            # Reorient the molecule
            oriented_mol, P = reoriented_molecule(mol)
            pga = PointGroupAnalyzer(oriented_mol)
        pg = pga.get_pointgroup()
        symm_m = []
        for op in pg:
            symm_m.append(op)
        # Add 12-fold  and reflections in place of ininitesimal rotation
        for axis in [[1, 0, 0], [0, 1, 0], [0, 0, 1]]:
            # op = SymmOp.from_rotation_and_translation(aa2matrix(axis, np.pi/6), [0,0,0])
            m1 = Rotation.from_rotvec(np.pi / 6 * axis).as_matrix()
            op = SymmOp.from_rotation_and_translation(m1, [0, 0, 0])
            if pga.is_valid_op(op):
                symm_m.append(op)
                # Any molecule with infinitesimal symmetry is linear;
                # Thus, it possess mirror symmetry for any axis perpendicular
                # To the rotational axis. pymatgen does not add this symmetry
                # for all linear molecules - for example, hydrogen
                if axis == [1, 0, 0]:
                    symm_m.append(SymmOp.from_xyz_string("x,-y,z"))
                    symm_m.append(SymmOp.from_xyz_string("x,y,-z"))
                    #r = SymmOp.from_xyz_string("-x,y,-z")
                elif axis == [0, 1, 0]:
                    symm_m.append(SymmOp.from_xyz_string("-x,y,z"))
                    symm_m.append(SymmOp.from_xyz_string("x,y,-z"))
                    #r = SymmOp.from_xyz_string("-x,-y,z")
                elif axis == [0, 0, 1]:
                    symm_m.append(SymmOp.from_xyz_string("-x,y,z"))
                    symm_m.append(SymmOp.from_xyz_string("x,-y,z"))
                    #r = SymmOp.from_xyz_string("x,-y,-z")
                # Generate a full list of SymmOps for the molecule's pointgroup
                symm_m = generate_full_symmops(symm_m, 1e-3)
                break
        # Reorient the SymmOps into mol's original frame
        if not already_oriented:
            new = []
            for op in symm_m:
                new.append(P.inverse * op * P)
            return new
        elif already_oriented:
            return symm_m
    # Handle nonlinear molecules
    else:
        pg = pga.get_pointgroup()
        symm_m = []
        for op in pg:
            symm_m.append(op)
        return symm_m
Ejemplo n.º 4
0
def orientation_in_wyckoff_position(mol,
                                    sg,
                                    index,
                                    randomize=True,
                                    exact_orientation=False,
                                    already_oriented=False,
                                    allow_inversion=False):
    '''
    Tests if a molecule meets the symmetry requirements of a Wyckoff position.
    If it does, return the rotation matrix needed. Otherwise, returns False.

    args:
        mol: pymatgen.core.structure.Molecule object. Orientation is arbitrary
        sg: the spacegroup to check
        index: the index of the Wyckoff position within the sg to check
        randomize: whether or not to apply a random rotation consistent with
            the symmetry requirements.
        exact_orientation: whether to only check compatibility for the provided
            orientation of the molecule. Used within general case for checking.
            If True, this function only returns True or False
        already_oriented: whether or not to reorient the principle axes
            when calling get_symmetry. Setting to True can remove redundancy,
            but is not necessary.
    '''
    #Obtain the Wyckoff symmetry
    symm_w = get_wyckoff_symmetry(sg, molecular=True)[index][0]
    pga = PointGroupAnalyzer(mol)

    #Check exact orientation
    if exact_orientation is True:
        mo = deepcopy(mol)
        valid = True
        for op in symm_w:
            if not pga.is_valid_op(op):
                valid = False
        if valid is True:
            return True
        elif valid is False:
            return False

    #Obtain molecular symmetry, exact_orientation==False
    symm_m = get_symmetry(mol, already_oriented=already_oriented)
    #Store OperationAnalyzer objects for each molecular SymmOp
    chiral = True
    opa_m = []
    for op_m in symm_m:
        opa = OperationAnalyzer(op_m)
        opa_m.append(opa)
        if opa.type == "rotoinversion":
            chiral = False
        elif opa.type == "inversion":
            chiral = False
    #If molecule is chiral and allow_inversion is False,
    #check if WP breaks symmetry
    if chiral is True:
        if allow_inversion is False:
            gen_pos = get_wyckoffs(sg)[0]
            for op in gen_pos:
                if np.linalg.det(op.rotation_matrix) < 0:
                    print(
                        "Warning: cannot place chiral molecule in spagegroup #"
                        + str(sg))
                    return False
    #Store OperationAnalyzer objects for each Wyckoff symmetry SymmOp
    opa_w = []
    for op_w in symm_w:
        opa_w.append(OperationAnalyzer(op_w))

    #Check for constraints from the Wyckoff symmetry...
    #If we find ANY two constraints (SymmOps with unique axes), the molecule's
    #point group MUST contain SymmOps which can be aligned to these particular
    #constraints. However, there may be multiple compatible orientations of the
    #molecule consistent with these constraints
    constraint1 = None
    constraint2 = None
    for i, op_w in enumerate(symm_w):
        if opa_w[i].axis is not None:
            constraint1 = opa_w[i]
            for j, op_w in enumerate(symm_w):
                if opa_w[j].axis is not None:
                    dot = np.dot(opa_w[i].axis, opa_w[j].axis)
                    if (not np.isclose(dot, 1, rtol=.01)) and (not np.isclose(
                            dot, -1, rtol=.01)):
                        constraint2 = opa_w[j]
                        break
            break
    #Indirectly store the angle between the constraint axes
    if (constraint1 is not None and constraint2 is not None):
        dot_w = np.dot(constraint1.axis, constraint2.axis)
    #Generate 1st consistent molecular constraints
    constraints_m = []
    if constraint1 is not None:
        for i, opa1 in enumerate(opa_m):
            if opa1.is_conjugate(constraint1):
                constraints_m.append([opa1, []])
                #Generate 2nd constraint in opposite direction
                extra = deepcopy(opa1)
                extra.axis = [
                    opa1.axis[0] * -1, opa1.axis[1] * -1, opa1.axis[2] * -1
                ]
                constraints_m.append([extra, []])

    #Remove redundancy for the first constraints
    list_i = list(range(len(constraints_m)))
    list_j = list(range(len(constraints_m)))
    copy = deepcopy(constraints_m)
    for i, c1 in enumerate(copy):
        if i in list_i:
            for j, c2 in enumerate(copy):
                if i > j and j in list_j and j in list_i:
                    #Check if axes are colinear
                    if np.isclose(np.dot(c1[0].axis, c2[0].axis), 1, rtol=.01):
                        list_i.remove(j)
                        list_j.remove(j)
                    else:  # np.isclose(np.dot(c1[0].axis, c2[0].axis), -1, rtol=.01):
                        cond1 = False
                        cond2 = False
                        for opa in opa_m:
                            if opa.type == "rotation":
                                op = opa.op
                                if np.isclose(np.dot(op.operate(c1[0].axis),
                                                     c2[0].axis),
                                              1,
                                              rtol=.05):
                                    cond1 = True
                                    break
                        if cond1 is True:  # or cond2 is True:
                            list_i.remove(j)
                            list_j.remove(j)
    c_m = deepcopy(constraints_m)
    constraints_m = []
    for i in list_i:
        constraints_m.append(c_m[i])

    #Generate 2nd consistent molecular constraints
    valid = range(len(constraints_m))
    if constraint2 is not None:
        for i, c in enumerate(constraints_m):
            opa1 = c[0]
            for j, opa2 in enumerate(opa_m):
                if opa2.is_conjugate(constraint2):
                    dot_m = np.dot(opa1.axis, opa2.axis)
                    #Ensure that the angles are equal
                    if abs(dot_m - dot_w) < .02 or abs(dot_m + dot_w) < .02:
                        constraints_m[i][1].append(opa2)
                        #Generate 2nd constraint in opposite direction
                        extra = deepcopy(opa2)
                        extra.axis = [
                            opa2.axis[0] * -1, opa2.axis[1] * -1,
                            opa2.axis[2] * -1
                        ]
                        constraints_m[i][1].append(extra)
            #If no consistent constraints are found, remove first constraint
            if constraints_m[i][1] == []:
                valid.remove(i)
    copy = deepcopy(constraints_m)
    constraints_m = []
    for i in valid:
        constraints_m.append(copy[i])

    #Generate orientations consistent with the possible constraints
    orientations = []
    #Loop over molecular constraint sets
    for c1 in constraints_m:
        v1 = c1[0].axis
        v2 = constraint1.axis
        T = rotate_vector(v1, v2)
        #Loop over second molecular constraints
        for opa in c1[1]:
            phi = angle(constraint1.axis, constraint2.axis)
            phi2 = angle(constraint1.axis, np.dot(T, opa.axis))
            if isclose(phi, phi2, rtol=.01):
                r = np.sin(phi)
                c = np.linalg.norm(np.dot(T, opa.axis) - constraint2.axis)
                theta = np.arccos(1 - (c**2) / (2 * (r**2)))
                R = aa2matrix(constraint1.axis, theta)
                T2 = np.dot(R, T)
                a = angle(np.dot(T2, opa.axis), constraint2.axis)
                if not np.isclose(a, 0, rtol=.01):
                    T2 = np.dot(np.linalg.inv(R), T)
                a = angle(np.dot(T2, opa.axis), constraint2.axis)
                if not np.isclose(a, 0, rtol=.01):
                    print("Error: Generated incorrect rotation: " + str(theta))
                o = orientation(T2, degrees=0)
                orientations.append(o)
        #If there is only one constraint
        if c1[1] == []:
            o = orientation(T, degrees=1, axis=constraint1.axis)
            orientations.append(o)
    #Ensure the identity orientation is checked if no constraints are found
    if constraints_m == []:
        o = orientation(np.identity(3), degrees=2)
        orientations.append(o)

    #Remove redundancy from orientations
    list_i = list(range(len(orientations)))
    list_j = list(range(len(orientations)))
    for i, o1 in enumerate(orientations):
        if i in list_i:
            for j, o2 in enumerate(orientations):
                if i > j and j in list_j and j in list_i:
                    m1 = o1.get_matrix(angle=0)
                    m2 = o2.get_matrix(angle=0)
                    new_op = SymmOp.from_rotation_and_translation(
                        np.dot(m2, np.linalg.inv(m1)), [0, 0, 0])
                    P = SymmOp.from_rotation_and_translation(
                        np.linalg.inv(m1), [0, 0, 0])
                    old_op = P * new_op * P.inverse
                    if pga.is_valid_op(old_op):
                        list_i.remove(j)
                        list_j.remove(j)
    copy = deepcopy(orientations)
    orientations = []
    for i in list_i:
        orientations.append(copy[i])

    #Check each of the found orientations for consistency with the Wyckoff pos.
    #If consistent, put into an array of valid orientations
    allowed = []
    for o in orientations:
        if randomize is True:
            op = o.get_op()
        elif randomize is False:
            op = o.get_op(angle=0)
        mo = deepcopy(mol)
        mo.apply_operation(op)
        if orientation_in_wyckoff_position(
                mo,
                sg,
                index,
                exact_orientation=True,
                already_oriented=already_oriented) is True:
            allowed.append(o)
    #Return the array of allowed orientations. If there are none, return False
    if allowed == []:
        return False
    else:
        return allowed
Ejemplo n.º 5
0
def orientation_in_wyckoff_position(mol,
                                    sg,
                                    index,
                                    randomize=True,
                                    exact_orientation=False,
                                    already_oriented=False):
    '''
    Tests if a molecule meets the symmetry requirements of a Wyckoff position.
    If it does, return the rotation matrix needed. Otherwise, returns False.

    args:
        mol: pymatgen.core.structure.Molecule object. Orientation is arbitrary
        sg: the spacegroup to check
        index: the index of the Wyckoff position within the sg to check
        randomize: whether or not to apply a random rotation consistent with
            the symmetry requirements.
        exact_orientation: whether to only check compatibility for the provided
            orientation of the molecule. Used within general case for checking.
            If True, this function only returns True or False
    '''
    #Obtain the Wyckoff symmetry
    symm_w_unedited = get_wyckoff_symmetry(sg)[index][0]
    found_params = []
    symm_w_partial = []
    symm_w = []
    orthogonal = True
    #Check if operations are orthogonal; 3-fold and 6-fold operations are not
    for op in symm_w_unedited:
        m1 = np.dot(op.rotation_matrix, np.transpose(op.rotation_matrix))
        m2 = np.dot(np.transpose(op.rotation_matrix), op.rotation_matrix)
        if (not allclose(m1, np.identity(3))) or (not allclose(
                m2, np.identity(3))):
            rotation_order = 0
            d = np.linalg.det(op.rotation_matrix)
            if isclose(d, 1):
                op_type = "rotation"
            elif isclose(d, -1):
                op_type = "improper rotation"
            if op_type == "rotation":
                newop = deepcopy(op)
                for n in range(1, 7):
                    newop = (newop * op)
                    if allclose(newop.rotation_matrix,
                                np.identity(3),
                                rtol=1e-2):
                        rotation_order = n + 1
                        break
            elif op_type == "improper rotation":
                #We only want the order of the rotational part of op,
                #So we multiply op by -1 for rotoinversions
                op_1 = SymmOp.from_rotation_and_translation(
                    op.rotation_matrix * -1, [0, 0, 0])
                new_op = deepcopy(op_1)
                for n in range(1, 7):
                    newop = (newop * op_1)
                    if allclose(newop.rotation_matrix, np.identity(3)):
                        rotation_order = n + 1
                        break
            if rotation_order == 0:
                print("Error: could not convert to orthogonal operation:")
                print(op)
                symm_w_partial.append(op)
            else:
                params = [rotation_order, op_type]
                if params not in found_params:
                    found_params.append(params)
        else:
            symm_w_partial.append(op)
    #Add 3-fold and 6-fold symmetry back in using absolute coordinates
    for params in found_params:
        order = params[0]
        op_type = params[1]
        if op_type == "rotation" and (order == 3 or order == 6):
            m = aa2matrix([0, 0, 1], (2 * pi) / order)
        elif op_type == "improper rotation" and (order == 3 or order == 6):
            m = aa2matrix([0, 0, 1], 2 * pi / order)
            m[2] *= -1
        symm_w_partial.append(
            SymmOp.from_rotation_and_translation(m, [0, 0, 0]))
    symm_w = generate_full_symmops(symm_w_partial, 1e-3)
    #Check exact orientation
    if exact_orientation is True:
        mo = deepcopy(mol)
        pga = PointGroupAnalyzer(mo)
        valid = True
        for op in symm_w:
            op_w = SymmOp.from_rotation_and_translation(
                op.rotation_matrix, [0, 0, 0])
            if not pga.is_valid_op(op_w):
                valid = False
        if valid is True:
            return True
        elif valid is False:
            return False
    #Obtain molecular symmetry, exact_orientation==False
    symm_m = get_symmetry(mol, already_oriented=already_oriented)
    #Store OperationAnalyzer objects for each SymmOp
    opa_w = []
    for op_w in symm_w:
        opa_w.append(OperationAnalyzer(op_w))
    opa_m = []
    for op_m in symm_m:
        opa_m.append(OperationAnalyzer(op_m))
    #Check for constraints from the Wyckoff symmetry...
    #If we find ANY two constraints (SymmOps with unique axes), the molecule's
    #point group MUST contain SymmOps which can be aligned to these particular
    #constraints. However, there may be multiple compatible orientations of the
    #molecule consistent with these constraints
    constraint1 = None
    constraint2 = None
    for i, op_w in enumerate(symm_w):
        if opa_w[i].axis is not None:
            constraint1 = opa_w[i]
            for j, op_w in enumerate(symm_w):
                if opa_w[j].axis is not None:
                    dot = np.dot(opa_w[i].axis, opa_w[j].axis)
                    if (not isclose(dot, 1)) and (not isclose(dot, -1)):
                        constraint2 = opa_w[j]
                        break
            break
    #Indirectly store the angle between the constraint axes
    if (constraint1 is not None and constraint2 is not None):
        dot_w = np.dot(constraint1.axis, constraint2.axis)
    #Store corresponding symmetry elements of the molecule
    constraints_m = []
    if constraint1 is not None:
        for i, op_m in enumerate(symm_m):
            if opa_m[i].is_conjugate(constraint1):
                constraints_m.append([opa_m[i], []])
                if constraint2 is not None:
                    for j, op_m2 in enumerate(symm_m):
                        if opa_m[j].is_conjugate(constraint2):
                            dot_m = np.dot(opa_m[i].axis, opa_m[j].axis)
                            #Ensure that the angles are equal
                            if isclose(dot_m, dot_w):
                                constraints_m[-1][1].append(opa_m[j])
    #Eliminate redundancy for 1st constraint
    list_i = list(range(len(constraints_m)))
    list_j = list(range(len(constraints_m)))
    for i, c1 in enumerate(constraints_m):
        if i in list_i:
            for j, c2 in enumerate(constraints_m):
                if i > j and j in list_j and j in list_i:
                    #check if c1 and c2 are symmetrically equivalent
                    #calculate moments of inertia about both axes
                    m1_a = get_moment_of_inertia(mol, c1[0].axis)
                    m2_a = get_moment_of_inertia(mol, c2[0].axis)
                    m1_b = get_moment_of_inertia(mol, c1[0].axis, scale=2.0)
                    m2_b = get_moment_of_inertia(mol, c2[0].axis, scale=2.0)
                    if isclose(m1_a, m2_a, rtol=1e-2) and isclose(
                            m1_b, m2_b, rtol=1e-2):
                        list_i.remove(j)
                        list_j.remove(j)
    c_m = deepcopy(constraints_m)
    constraints_m = []
    for i in list_i:
        constraints_m.append(c_m[i])
    #Eliminate redundancy for second constraint
    for k, c in enumerate(constraints_m):
        if c[1] != []:
            list_i = list(range(len(c[1])))
            list_j = list(range(len(c[1])))
            for i, c1 in enumerate(c[1]):
                if i in list_i:
                    for j, c2 in enumerate(c[1]):
                        if j > i and j in list_j and j in list_i:
                            #check if c1 and c2 are symmetrically equivalent
                            #calculate moments of inertia about both axes
                            m1_a = get_moment_of_inertia(mol, c1.axis)
                            m2_a = get_moment_of_inertia(mol, c2.axis)
                            m1_b = get_moment_of_inertia(mol,
                                                         c1.axis,
                                                         scale=2.0)
                            m2_b = get_moment_of_inertia(mol,
                                                         c2.axis,
                                                         scale=2.0)
                            if isclose(m1_a, m2_a, rtol=1e-2) and isclose(
                                    m1_b, m2_b, rtol=1e-2):
                                list_i.remove(j)
                                list_j.remove(j)
            c_m = deepcopy(c[1])
            constraints_m[k][1] = []
            for i in list_i:
                constraints_m[k][1].append(c_m[i])
    #Generate orientations consistent with the possible constraints
    orientations = []
    #Loop over molecular constraint sets
    for c1 in constraints_m:
        v1 = c1[0].axis
        v2 = constraint1.axis
        T = rotate_vector(v1, v2)
        #Loop over second molecular constraints
        for opa in c1[1]:
            v1 = np.dot(T, opa.axis)
            v2 = constraint2.axis
            R = rotate_vector(v1, v2)
            T2 = np.dot(T, R)
            orientations.append(T2)
        if c1[1] == []:
            if randomize is True:
                angle = rand() * 2 * pi
                R = aa2matrix(v1, angle)
                T2 = np.dot(T, R)
                orientations.append(T2)
            else:
                orientations.append(T)
    #Ensure the identity orientation is checked if no constraints are found
    if constraints_m == []:
        if randomize is True:
            R = aa2matrix(1, 1, random=True)
            orientations.append(R)
        else:
            orientations.append(np.identity(3))
    #Check each of the found orientations for consistency with the Wyckoff pos.
    #If consistent, put into an array of valid orientations
    allowed = []
    for o in orientations:
        o = SymmOp.from_rotation_and_translation(o, [0, 0, 0])
        mo = deepcopy(mol)
        mo.apply_operation(o)
        if orientation_in_wyckoff_position(
                mo,
                sg,
                index,
                exact_orientation=True,
                already_oriented=already_oriented) is True:
            allowed.append(o)
    #Return the array of allowed orientations. If there are none, return False
    if allowed == []:
        return False
    else:
        return allowed