import numpy as np
from numpy.linalg import matrix_power
from .point_group import PointGroup
from .sym_ops import reflection_matrix, inversion_matrix, Cn, Sn, normalize, reduce
from .symel import Symel

np.set_printoptions(precision=3, threshold=np.inf, linewidth=14000, suppress=True)


# ── Cubic and icosahedral group generators ────────────────────────────────────

def generate_T():
    """
    Generate symmetry elements for the T point group.
    Assumes a tetrahedron contained in a cube.

    Returns
    -------
    List[Symel]
    """
    symels = [Symel("E", None, np.eye(3))]
    C3_1v = normalize(np.array([1.0, 1.0, 1.0]))
    C3_2v = normalize(np.array([-1.0, 1.0, -1.0]))
    C3_3v = normalize(np.array([-1.0, -1.0, 1.0]))
    C3_4v = normalize(np.array([1.0, -1.0, -1.0]))
    C3list = [C3_1v, C3_2v, C3_3v, C3_4v]
    namelist = ("alpha", "beta", "gamma", "delta")
    for i in range(4):
        C3 = Cn(C3list[i], 3)
        C3s = matrix_power(C3, 2)
        symels.append(Symel(f"C_3({namelist[i]})", C3list[i], C3))
        symels.append(Symel(f"C_3^2({namelist[i]})", C3list[i], C3s))
    C2list = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])]
    namelist = ["x", "y", "z"]
    for i in range(3):
        C2 = Cn(C2list[i], 2)
        symels.append(Symel(f"C_2({namelist[i]})", C2list[i], C2))
    return symels


def generate_Td():
    """
    Generate symmetry elements for the Td point group.
    Assumes a tetrahedron contained in a cube.

    Returns
    -------
    List[Symel]
    """
    symels = generate_T()
    sigmas = [
        normalize(np.array([1.0, 1.0, 0.0])), normalize(np.array([1.0, -1.0, 0.0])),
        normalize(np.array([1.0, 0.0, 1.0])), normalize(np.array([1.0, 0.0, -1.0])),
        normalize(np.array([0.0, 1.0, 1.0])), normalize(np.array([0.0, 1.0, -1.0])),
    ]
    namelist = ["xyp", "xym", "xzp", "xzm", "yzp", "yzm"]
    for i in range(6):
        symels.append(Symel(f"sigma_d({namelist[i]})", sigmas[i], reflection_matrix(sigmas[i])))
    S4list = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])]
    namelist = ["x", "y", "z"]
    for i in range(3):
        S4 = Sn(S4list[i], 4)
        symels.append(Symel(f"S_4({namelist[i]})", S4list[i], S4))
        symels.append(Symel(f"S_4^3({namelist[i]})", S4list[i], matrix_power(S4, 3)))
    return symels


def generate_Th():
    """
    Generate symmetry elements for the Th point group.
    Assumes a tetrahedron contained in a cube.

    Returns
    -------
    List[Symel]
    """
    symels = generate_T()
    symels.append(Symel("i", None, inversion_matrix()))
    S6list = [
        normalize(np.array([1.0, 1.0, 1.0])), normalize(np.array([-1.0, 1.0, -1.0])),
        normalize(np.array([-1.0, -1.0, 1.0])), normalize(np.array([1.0, -1.0, -1.0])),
    ]
    namelist = ["alpha", "beta", "gamma", "delta"]
    for i in range(4):
        S6 = Sn(S6list[i], 6)
        symels.append(Symel(f"S_6({namelist[i]})", S6list[i], S6))
        symels.append(Symel(f"S_6^5({namelist[i]})", S6list[i], matrix_power(S6, 5)))
    sigma_list = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])]
    namelist = ["x", "y", "z"]
    for i in range(3):
        symels.append(Symel(f"sigma_h({namelist[i]})", sigma_list[i], reflection_matrix(sigma_list[i])))
    return symels


def generate_O():
    """
    Generate symmetry elements for the O point group.
    Assumes operations on a cube.

    Returns
    -------
    List[Symel]
    """
    symels = [Symel("E", None, np.eye(3))]
    C4list = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])]
    namelist = ["x", "y", "z"]
    for i in range(3):
        C4 = Cn(C4list[i], 4)
        symels.append(Symel(f"C_4({namelist[i]})", C4list[i], C4))
        symels.append(Symel(f"C_2({namelist[i]})", C4list[i], matrix_power(C4, 2)))
        symels.append(Symel(f"C_4^3({namelist[i]})", C4list[i], matrix_power(C4, 3)))
    C3list = [
        normalize(np.array([1.0, 1.0, 1.0])), normalize(np.array([1.0, -1.0, 1.0])),
        normalize(np.array([1.0, 1.0, -1.0])), normalize(np.array([1.0, -1.0, -1.0])),
    ]
    namelist = ["alpha", "beta", "gamma", "delta"]
    for i in range(4):
        C3 = Cn(C3list[i], 3)
        symels.append(Symel(f"C_3({namelist[i]})", C3list[i], C3))
        symels.append(Symel(f"C_3^2({namelist[i]})", C3list[i], matrix_power(C3, 2)))
    C2list = [
        normalize(np.array([1.0, 0.0, 1.0])), normalize(np.array([1.0, 0.0, -1.0])),
        normalize(np.array([1.0, 1.0, 0.0])), normalize(np.array([1.0, -1.0, 0.0])),
        normalize(np.array([0.0, 1.0, 1.0])), normalize(np.array([0.0, -1.0, 1.0])),
    ]
    namelist = ["xzp", "xzm", "xyp", "xym", "yzp", "yzm"]
    for i in range(6):
        symels.append(Symel(f"C_2({namelist[i]})", C2list[i], Cn(C2list[i], 2)))
    return symels


def generate_Oh():
    """
    Generate symmetry elements for the Oh point group.
    Assumes operations on a cube.

    Returns
    -------
    List[Symel]
    """
    symels = generate_O()
    symels.append(Symel("i", None, inversion_matrix()))
    S4list = [np.array([1.0, 0.0, 0.0]), np.array([0.0, 1.0, 0.0]), np.array([0.0, 0.0, 1.0])]
    namelist = ["x", "y", "z"]
    for i in range(3):
        S4 = Sn(S4list[i], 4)
        symels.append(Symel(f"S_4({namelist[i]})", S4list[i], S4))
        symels.append(Symel(f"sigma_h({namelist[i]})", S4list[i], reflection_matrix(S4list[i])))
        symels.append(Symel(f"S_4^3({namelist[i]})", S4list[i], matrix_power(S4, 3)))
    S6list = [
        normalize(np.array([1.0, 1.0, 1.0])), normalize(np.array([1.0, -1.0, 1.0])),
        normalize(np.array([1.0, 1.0, -1.0])), normalize(np.array([1.0, -1.0, -1.0])),
    ]
    namelist = ["alpha", "beta", "gamma", "delta"]
    for i in range(4):
        S6 = Sn(S6list[i], 6)
        symels.append(Symel(f"S_6({namelist[i]})", S6list[i], S6))
        symels.append(Symel(f"S_6^5({namelist[i]})", S6list[i], matrix_power(S6, 5)))
    sigma_dlist = [
        normalize(np.array([1.0, 0.0, 1.0])), normalize(np.array([1.0, 0.0, -1.0])),
        normalize(np.array([1.0, 1.0, 0.0])), normalize(np.array([1.0, -1.0, 0.0])),
        normalize(np.array([0.0, 1.0, 1.0])), normalize(np.array([0.0, -1.0, 1.0])),
    ]
    namelist = ["xzp", "xzm", "xyp", "xym", "yzp", "yzm"]
    for i in range(6):
        symels.append(Symel(f"sigma_d({namelist[i]})", sigma_dlist[i], reflection_matrix(sigma_dlist[i])))
    return symels


def generate_I():
    """
    Generate symmetry elements for the I point group.

    Returns
    -------
    List[Symel]
    """
    symels = [Symel("E", None, np.eye(3))]
    faces, vertices, edgecenters = _icosahedron_vectors()
    for i in range(6):
        C5 = Cn(faces[i], 5)
        symels.append(Symel(f"C_5({i})", faces[i], C5))
        symels.append(Symel(f"C_5^2({i})", faces[i], matrix_power(C5, 2)))
        symels.append(Symel(f"C_5^3({i})", faces[i], matrix_power(C5, 3)))
        symels.append(Symel(f"C_5^4({i})", faces[i], matrix_power(C5, 4)))
    for i in range(10):
        C3 = Cn(vertices[i], 3)
        symels.append(Symel(f"C_3({i})", vertices[i], C3))
        symels.append(Symel(f"C_3^2({i})", vertices[i], matrix_power(C3, 2)))
    for i in range(15):
        symels.append(Symel(f"C_2({i})", edgecenters[i], Cn(edgecenters[i], 2)))
    return symels


def generate_Ih():
    """
    Generate symmetry elements for the Ih point group.

    Returns
    -------
    List[Symel]
    """
    symels = generate_I()
    faces, vertices, edgecenters = _icosahedron_vectors()
    symels.append(Symel("i", None, inversion_matrix()))
    for i in range(6):
        S10 = Sn(faces[i], 10)
        symels.append(Symel(f"S_10({i})", faces[i], S10))
        symels.append(Symel(f"S_10^3({i})", faces[i], matrix_power(S10, 3)))
        symels.append(Symel(f"S_10^7({i})", faces[i], matrix_power(S10, 7)))
        symels.append(Symel(f"S_10^9({i})", faces[i], matrix_power(S10, 9)))
    for i in range(10):
        S6 = Sn(vertices[i], 6)
        symels.append(Symel(f"S_6({i})", vertices[i], S6))
        symels.append(Symel(f"S_6^5({i})", vertices[i], matrix_power(S6, 5)))
    for i in range(15):
        symels.append(Symel(f"sigma({i})", edgecenters[i], reflection_matrix(edgecenters[i])))
    return symels


def _icosahedron_vectors():
    """
    Vectors defining the faces, vertices, and edge centers of a regular icosahedron/dodecahedron.

    Returns
    -------
    tuple(List[np.array], List[np.array], List[np.array])
        Face axes (6), vertex axes (10), and edge-center axes (15).
    """
    s5   = np.sqrt(5)
    phi  = (1 + s5) / 2           # golden ratio phi = (1+sqrt(5))/2

    r5   = 1 / s5                 # 1/sqrt(5), appears 7 times
    r3   = 1 / np.sqrt(3)         # 1/sqrt(3), appears 4 times

    sp10 = np.sqrt((5 + s5) / 10) # sqrt((5+sqrt(5))/10), appears 10 times
    sm10 = np.sqrt((5 - s5) / 10) # sqrt((5-sqrt(5))/10), appears 10 times
    vp   = np.sqrt((5 + 2*s5) / 15) # sqrt((5+2*sqrt(5))/15), appears 5 times
    vm   = np.sqrt((5 - 2*s5) / 15) # sqrt((5-2*sqrt(5))/15), appears 5 times
    sp30 = np.sqrt((5 + s5) / 30) # sqrt((5+sqrt(5))/30), appears 2 times
    sm30 = np.sqrt((5 - s5) / 30) # sqrt((5-sqrt(5))/30), appears 2 times
    t3p  = np.sqrt((3*s5 + 5) / (6*s5)) # sqrt((3*sqrt(5)+5)/(6*sqrt(5))), appears 2 times
    t3m  = np.sqrt((3*s5 - 5) / (6*s5)) # sqrt((3*sqrt(5)-5)/(6*sqrt(5))), appears 2 times

    phim = 1 / (2*phi)            # (sqrt(5)-1)/4, appears 4 times
    phip = phi / 2                # (sqrt(5)+1)/4, appears 4 times
    sp2h = np.sqrt((5 + s5) / 2) / 2  # sqrt((5+sqrt(5))/2)/2, appears 2 times
    sm2h = np.sqrt((5 - s5) / 2) / 2  # sqrt((5-sqrt(5))/2)/2, appears 2 times
    a1p  = np.sqrt(1 + 2*r5) / 2 # sqrt(1+2/sqrt(5))/2, appears 2 times
    a1m  = np.sqrt(1 - 2*r5) / 2 # sqrt(1-2/sqrt(5))/2, appears 2 times

    faces = [
        np.array([0.0,   0.0,        1.0  ]),
        np.array([0.0,   2*r5,       r5   ]),
        np.array([-sp10, (5-s5)/10,  r5   ]),
        np.array([ sp10, (5-s5)/10,  r5   ]),
        np.array([-sm10, -(5+s5)/10, r5   ]),
        np.array([ sm10, -(5+s5)/10, r5   ]),
    ]
    vertices = [
        np.array([0.0,  -np.sqrt(2*(5+s5)/15), vm  ]),
        np.array([0.0,  -np.sqrt(2*(5-s5)/15), vp  ]),
        np.array([-r3,   vp,                   vm  ]),
        np.array([-r3,  -vm,                   vp  ]),
        np.array([ r3,   vp,                   vm  ]),
        np.array([ r3,  -vm,                   vp  ]),
        np.array([-t3p, -sm30,                 vm  ]),
        np.array([ t3m,  sp30,                 vp  ]),
        np.array([-t3m,  sp30,                 vp  ]),
        np.array([ t3p, -sm30,                 vm  ]),
    ]
    edgecenters = [
        np.array([0.0,   sp10,  -sm10]),
        np.array([0.0,   sm10,   sp10]),
        np.array([0.5,  -a1p,   -sm10]),
        np.array([0.5,   a1m,    sp10]),
        np.array([0.5,  -a1m,   -sp10]),
        np.array([0.5,   a1p,    sm10]),
        np.array([1.0,   0.0,    0.0 ]),
        np.array([phim, -sp10/2, sp10]),
        np.array([phim,  sp10/2,-sp10]),
        np.array([phim, -sp2h,   0.0 ]),
        np.array([phim,  sp2h,   0.0 ]),
        np.array([phip, -sm10/2, sm10]),
        np.array([phip,  sm10/2,-sm10]),
        np.array([phip, -sm2h,   0.0 ]),
        np.array([phip,  sm2h,   0.0 ]),
    ]
    return faces, vertices, edgecenters


# ── Cyclic and dihedral group generators ─────────────────────────────────────

def pg_to_symels(PG):
    """
    Return the list of symmetry elements for the given point group.

    Parameters
    ----------
    PG : str
        Schoenflies point group symbol (e.g. "C2v", "D6h", "Oh").

    Returns
    -------
    List[Symel]
    """
    pg = PointGroup.from_string(PG)
    argerr = f"Invalid point group or unexpected parse result: {pg.str}"
    z = np.array([0,0,1])
    i = Symel("i", None, inversion_matrix(), 0, 0, "i")
    sh = Symel("sigma_h", np.array([0,0,1]), reflection_matrix(z), 0, 0, "sigma_h")
    if pg.is_linear:
        if pg.family == "C":
            return [Symel("C", z, None, None, None, None),
                    Symel("sigma_v", None, None, None, None, None)]
        elif pg.family == "D":
            return [Symel("C", z, None, None, None, None),
                    Symel("sigma_v", None, None, None, None, None),
                    Symel("S", z, None, None, None, None),
                    Symel("C_2'", None, None, None, None, None)]
    if pg.n is not None:
        n_is_even = (pg.n % 2 == 0)
        n_is_doubleeven = (pg.n % 4 == 0)
    if pg.family == "C":
        if pg.subfamily == "h":
            cn_symels = _Zn(pg.n, "C")
            if n_is_even:
                return _direct_product(cn_symels, i)
            else:
                return _direct_product(cn_symels, sh)
        elif pg.subfamily == "v":
            return _Dihn(pg.n, "C", "sigma_v")
        elif pg.subfamily == "s":
            return [Symel("E", None, np.eye(3), 0, None, "E"), sh]
        elif pg.subfamily == "i":
            return [Symel("E", None, np.eye(3), 0, None, "E"), i]
        elif pg.subfamily is None:
            return _Zn(pg.n, "C")
        else:
            raise Exception(argerr)
    elif pg.family == "D":
        if pg.subfamily == "h":
            dn_symels = _Dihn(pg.n, "C", "C_2'")
            if n_is_even:
                return _direct_product(dn_symels, i)
            else:
                return _direct_product(dn_symels, sh)
        elif pg.subfamily == "d":
            if n_is_even:
                return _Dihn(pg.n*2, "S", "C_2'")
            else:
                dn_symels = _Dihn(pg.n, "C", "C_2'")
                return _direct_product(dn_symels, i)
        elif pg.subfamily is None:
            return _Dihn(pg.n, "C", "C_2'")
        else:
            raise Exception(argerr)
    elif pg.family == "S":
        if pg.subfamily is None and n_is_even:
            if n_is_doubleeven:
                return _Zn(pg.n, "S")
            else:
                cn_symels = _Zn(pg.n>>1, "C")
                return _direct_product(cn_symels, i)
        else:
            raise Exception(argerr)
    else:
        if pg.family == "T":
            if pg.subfamily == "h":
                return generate_Th()
            elif pg.subfamily == "d":
                return generate_Td()
            else:
                return generate_T()
        elif pg.family == "O":
            if pg.subfamily == "h":
                return generate_Oh()
            else:
                return generate_O()
        elif pg.family == "I":
            if pg.subfamily == "h":
                return generate_Ih()
            else:
                return generate_I()
        else:
            raise Exception(argerr)
    return 0


def _Zn(n, generator):
    """
    Build symmetry elements for cyclic groups generated by C_n or S_n.

    Parameters
    ----------
    n : int
        Order of the principal axis.
    generator : str
        Principal generator; either "C" for proper rotations or "S" for improper rotations.

    Returns
    -------
    List[Symel]
    """
    x, y, z = np.eye(3)
    symels = [Symel("E", None, np.eye(3, dtype=np.float64), 0, n, "E")]
    if generator == "C" and n > 1:
        cn1 = Symel(f"C_{n}", z, Cn(z, n), 1, n, "E")
        symels.append(cn1)
        for m in range(2, n):
            a, b = reduce(n, m)
            symb = f"C_{a}" if b == 1 else f"C_{a}^{b}"
            symels.append(Symel(symb, z, matrix_power(Cn(z, n), m), m, n, "E"))
    elif generator == "S":
        sn1 = Symel(f"S_{n}", z, Sn(z, n), 1, n, "E")
        symels.append(sn1)
        for m in range(2, n):
            a, b = reduce(n, m)
            symb = f"C_{a}^{b}" if m % 2 == 0 else f"S_{a}^{b}"
            if b == 1:
                symb = symb.replace("^1", "")
            symels.append(Symel(symb, z, matrix_power(Sn(z, n), m), m, n, "E"))
    return symels


def _omega(m, n):
    """Reduce the power m of an S_n element to its canonical symbol index and axis order."""
    gcd_val = np.gcd(m, n)
    l = (m/gcd_val) + (n/gcd_val) * (1-((m/gcd_val)%2))
    return int(l), int(n/gcd_val)


def _mult_iCnm(m, n):
    """Return the canonical power and axis order of i * C_n^m."""
    a = (2*m+n) % (2*n)
    return _omega(a, 2*n)


def _mult_sigmahCnm(m, n):
    """Return the canonical power and axis order of sigma_h * C_n^m."""
    return _omega(m, n)


def _mult_CSC2sigma(m, n, pre, post):
    """Return the symbol of the product of a principal-axis element with a C_2' or sigma element."""
    if pre == "C":
        even_odd = ["'", "''"] if post == "C_2" else ["_v", "_d"]
    else:
        if post == "C_2":
            post = "sigma"
        even_odd = ["_v", "_d"]
    if n % 2 == 0:
        if pre == "iC":
            if n % 4 == 0:
                label = even_odd[m%2]
                a = ((n>>2) + (m>>1)) % (n>>1)
            else:
                label = even_odd[(m+1)%2]
                a = ((n>>2) + ((m+1)>>1)) % (n>>1)
            return post + label + f"({a})"
        else:
            label = even_odd[m%2]
            a = (m>>1) % (n>>1)
            return post + label + f"({a})"
    else:
        if pre == "iC":
            label = "_d"
            a = ((n>>1) + m) % n
        else:
            label = even_odd[0]
            a = ((((n>>1)+1) * (m % 2))+(m>>1)) % n
        return post + label + f"({a})"


def _Dihn(n, generator, symel_tag):
    """
    Build symmetry elements for dihedral-isomorphic groups.

    Parameters
    ----------
    n : int
        Order of the principal axis.
    generator : str
        Principal generator; either "C" for proper rotations or "S" for improper rotations.
    symel_tag : str
        Secondary generator label; either "sigma_v" or "C_2'".

    Returns
    -------
    List[Symel]
    """
    x, y, z = np.eye(3)
    symels = [Symel("E", None, np.eye(3, dtype=np.float64), 0, n, "E")]
    if generator == "C":
        if symel_tag == "sigma_v":
            post = "sigma"
            on0 = Symel("sigma_v(0)", y, reflection_matrix(y), 0, n, "sigma_v")
            tag_re = ("v", "d")
        elif symel_tag == "C_2'":
            post = "C_2"
            on0 = Symel(f"C_2'(0)", x, Cn(x, 2), 0, n, "C_2'")
            tag_re = ("'", "''")
        if n % 2 == 0:
            rsymb = symel_tag.replace(tag_re[0], tag_re[1]) + "(0)"
        else:
            rsymb = symel_tag + f"({(n>>1)+1})"
        symels.append(on0)
        for m in range(1, n):
            a, b = reduce(n, m)
            symb = f"C_{a}" if b == 1 else f"C_{a}^{b}"
            rot = matrix_power(Cn(z, n), m)
            symels.insert(m, Symel(symb, z, matrix_power(Cn(z, n), m), m, n, "E"))
            rsymb = _mult_CSC2sigma(m, n, "C", post)
            new_vector = np.dot(matrix_power(Cn(z, 2*n), m), on0.vector)
            symels.append(Symel(rsymb, new_vector, rot @ on0.rrep, m, n, symel_tag))
    elif generator == "S":
        sn1 = Symel(f"S_{n}", z, Sn(z, n), 1, n, "E")
        symels.append(sn1)
        on0 = Symel("C_2'(0)", x, Cn(x, 2), 0, n, "C_2'")
        symels.append(on0)
        v = np.dot(Cn(z, 2*n), y)
        on1 = Symel("sigma_d(0)", v, reflection_matrix(v), 1, n, "C_2'")
        symels.append(on1)
        for m in range(2, n):
            a, b = reduce(n, m)
            if m % 2 == 0:
                symb = f"C_{a}^{b}"
                rsymb = f"C_2'({m>>1})"
                rot = matrix_power(Cn(z, n), m)
                vec = np.dot(matrix_power(Cn(z, n), m>>1), on0.vector)
                rrep = rot @ on0.rrep
                insert_idx = -(m>>1)
            else:
                symb = f"S_{a}^{b}"
                rsymb = f"sigma_d({m>>1})"
                rot = matrix_power(Cn(z, n), m-1)
                vec = np.dot(matrix_power(Cn(z, n), m>>1), on1.vector)
                rrep = rot @ on1.rrep
                insert_idx = len(symels)+1
            if b == 1:
                symb = symb.replace("^1", "")
            symels.insert(m, Symel(symb, z, matrix_power(Sn(z, n), m), m, n, "E"))
            symels.insert(insert_idx, Symel(rsymb, vec, rrep, m, n, symel_tag))
    else:
        raise Exception(f"Invalid generator {generator}")
    return symels


def _direct_product(symels, dpsymel):
    """
    Extend a set of symels by taking the direct product with a group of order 2.

    Parameters
    ----------
    symels : List[Symel]
        Symmetry elements of the base group.
    dpsymel : Symel
        The non-identity element of the order-2 factor group (i or sigma_h).

    Returns
    -------
    List[Symel]
    """
    x, y, z = np.eye(3)
    newsymels = list(symels)
    for symel in symels:
        new_rrep = symel.rrep @ dpsymel.rrep
        if dpsymel.symbol == "i":
            if symel.O == "E":
                b, a = _mult_iCnm(symel.m, symel.n)
                if a == 2:
                    new_symbol, new_vector = "i", None
                elif a == 1:
                    new_symbol, new_vector = "sigma_h", z
                else:
                    new_symbol = f"S_{a}" if b == 1 else f"S_{a}^{b}"
                    new_vector = symel.vector
                new_O = dpsymel.symbol
            elif symel.O == "C_2'":
                new_symbol = _mult_CSC2sigma(symel.m, symel.n, "iC", "C_2")
                new_vector = symel.vector
                new_O = dpsymel.symbol
        elif dpsymel.symbol == "sigma_h":
            if symel.O == "E":
                new_O = "sigma_h"
                b, a = _mult_sigmahCnm(symel.m, symel.n)
                if a == 1:
                    new_symbol, new_vector = "sigma_h", z
                else:
                    new_symbol = f"S_{a}" if b == 1 else f"S_{a}^{b}"
                    new_vector = symel.vector
            elif symel.O == "C_2'":
                new_vector = normalize(np.cross(z, symel.vector))
                new_symbol = symel.symbol.replace("C_2'", "sigma_v")
                new_O = "sigma_v"
        else:
            raise Exception(f"Invalid direct product element: {dpsymel.symbol}")
        newsymels.append(Symel(new_symbol, new_vector, new_rrep, symel.m, symel.n, new_O))
    return newsymels
