Exemplo n.º 1
0
def translation_coefficients_svwf_out_to_out(tau1, l1, m1, tau2, l2, m2, k, d, sph_bessel=None, legendre=None,
                                             exp_immphi=None):
    r"""Coefficients of the translation operator for the expansion of an outgoing spherical wave in terms of
    outgoing spherical waves with respect to a different origin:

    .. math::
        \mathbf{\Psi}_{\tau l m}^{(3)}(\mathbf{r} + \mathbf{d} = \sum_{\tau'} \sum_{l'} \sum_{m'}
        A_{\tau l m, \tau' l' m'} (\mathbf{d}) \mathbf{\Psi}_{\tau' l' m'}^{(3)}(\mathbf{r})

    for :math:`|\mathbf{r}|>|\mathbf{d}|`.

    Args:
        tau1 (int):             tau1=0,1: Original wave's spherical polarization
        l1 (int):               l=1,...: Original wave's SVWF multipole degree
        m1 (int):               m=-l,...,l: Original wave's SVWF multipole order
        tau2 (int):             tau2=0,1: Partial wave's spherical polarization
        l2 (int):               l=1,...: Partial wave's SVWF multipole degree
        m2 (int):               m=-l,...,l: Partial wave's SVWF multipole order
        k (float or complex):   wavenumber (inverse length unit)
        d (list):               translation vectors in format [dx, dy, dz] (length unit)
                                dx, dy, dz can be scalars or ndarrays
        sph_bessel (list):      Optional. sph_bessel[i] contains the spherical Bessel funciton of degree i, evaluated at
                                k*d where d is the norm of the distance vector(s)
        legendre (list):        Optional. legendre[l][m] contains the legendre function of order l and degree m,
                                evaluated at cos(theta) where theta is the polar angle(s) of the distance vector(s)

    Returns:
        translation coefficient A (complex)
    """
    # spherical coordinates of d:
    dd = np.sqrt(d[0] ** 2 + d[1] ** 2 + d[2] ** 2)

    if exp_immphi is None:
        phid = np.arctan2(d[1], d[0])
        eimph = np.exp(1j * (m1 - m2) * phid)
    else:
        eimph = exp_immphi[m1][m2]

    if sph_bessel is None:
        sph_bessel = [sf.spherical_bessel(n, k * dd) for n in range(l1 + l2 + 1)]

    if legendre is None:
        costthetd = d[2] / dd
        sinthetd = np.sqrt(d[0] ** 2 + d[1] ** 2) / dd
        legendre, _, _ = sf.legendre_normalized(costthetd, sinthetd, l1 + l2)

    A = complex(0), complex(0)
    for ld in range(abs(l1 - l2), l1 + l2 + 1):
        a5, b5 = ab5_coefficients(l1, m1, l2, m2, ld)
        if tau1==tau2:
            A += a5 * sph_bessel[ld] * legendre[ld][abs(m1 - m2)]
        else:
            A += b5 * sph_bessel[ld] * legendre[ld][abs(m1 - m2)]
    A = eimph * A
    return A
Exemplo n.º 2
0
def transformation_coefficients_vwf(tau, l, m, pol, kp=None, kz=None, pilm_list=None, taulm_list=None, dagger=False):
    r"""Transformation coefficients B to expand SVWF in PVWF and vice versa:

    .. math::
        B_{\tau l m,j}(x) = -\frac{1}{\mathrm{i}^{l+1}} \frac{1}{\sqrt{2l(l+1)}} (\mathrm{i} \delta_{j1} + \delta_{j2})
        (\delta_{\tau j} \tau_l^{|m|}(x) + (1-\delta_{\tau j} m \pi_l^{|m|}(x))

    For the definition of the :math:`\tau_l^m` and :math:`\pi_l^m` functions, see
    `A. Doicu, T. Wriedt, and Y. A. Eremin: "Light Scattering by Systems of Particles", Springer-Verlag, 2006
    <https://doi.org/10.1007/978-3-540-33697-6>`_

    Args:
        tau (int):          SVWF polarization, 0 for spherical TE, 1 for spherical TM
        l (int):            l=1,... SVWF multipole degree
        m (int):            m=-l,...,l SVWF multipole order
        pol (int):          PVWF polarization, 0 for TE, 1 for TM
        kp (numpy array):         PVWF in-plane wavenumbers
        kz (numpy array):         complex numpy-array: PVWF out-of-plane wavenumbers
        pilm_list (list):   2D list numpy-arrays: alternatively to kp and kz, pilm and taulm as generated with
                            legendre_normalized can directly be handed
        taulm_list (list):  2D list numpy-arrays: alternatively to kp and kz, pilm and taulm as generated with
                            legendre_normalized can directly be handed
        dagger (bool):      switch on when expanding PVWF in SVWF and off when expanding SVWF in PVWF

    Returns:
        Transformation coefficient as array (size like kp).
    """
    if pilm_list is None:
        k = np.sqrt(kp**2 + kz**2)
        ct = kz / k
        st = kp / k
        plm_list, pilm_list, taulm_list = sf.legendre_normalized(ct, st, l)

    if tau == pol:
        sphfun = taulm_list[l][abs(m)]
    else:
        sphfun = m * pilm_list[l][abs(m)]

    if dagger:
        if pol == 0:
            prefac = -1 / (-1j) ** (l + 1) / np.sqrt(2 * l * (l + 1)) * (-1j)
        elif pol == 1:
            prefac = -1 / (-1j) ** (l + 1) / np.sqrt(2 * l * (l + 1)) * 1
        else:
            raise ValueError('pol must be 0 (TE) or 1 (TM)')
    else:
        if pol == 0:
            prefac = -1 / (1j) ** (l + 1) / np.sqrt(2 * l * (l + 1)) * (1j)
        elif pol ==1:
            prefac = -1 / (1j) ** (l + 1) / np.sqrt(2 * l * (l + 1)) * 1
        else:
            raise ValueError('pol must be 0 (TE) or 1 (TM)')

    B = prefac * sphfun
    return B
Exemplo n.º 3
0
def compute_J_cyl(lmax, Ntheta, a, h, n0, ns, wavelength, nu):
    #dimension of final T-matrix is 2*nmax x 2*nmax for each individual matrix
    nmax = int(lmax * (lmax + 2))

    #preallocate space for both J and dJ matrices of size nmax x nmax for J matrices
    #and dJ matrices are nmax x nmax x 2
    #dJ[:,:,0] is dJ/da
    #dJ[:,:,1] is dJ/dh
    J11 = np.zeros((nmax, nmax), dtype=np.complex_)
    J12 = np.zeros((nmax, nmax), dtype=np.complex_)
    J21 = np.zeros((nmax, nmax), dtype=np.complex_)
    J22 = np.zeros((nmax, nmax), dtype=np.complex_)

    dJ11 = np.zeros((nmax, nmax, 2), dtype=np.complex_)
    dJ12 = np.zeros((nmax, nmax, 2), dtype=np.complex_)
    dJ21 = np.zeros((nmax, nmax, 2), dtype=np.complex_)
    dJ22 = np.zeros((nmax, nmax, 2), dtype=np.complex_)

    #find the angle theta at which the corner of the cylinder is at
    theta_edge = np.arctan(2 * a / h)
    #prepare gauss-legendre quadrature for interval of [-1,1] to perform numerical integral
    [x_norm, wt_norm] = legendre.leggauss(Ntheta)

    #rescale integration points and weights to match actual bounds:
    #   circ covers the circular surface of the cylinder (end caps)
    #   body covers the rectangular surface of the cylinder (body area)
    #circ integral goes from 0 to theta_edge, b = theta_edge, a = 0
    theta_circ = theta_edge / 2 * x_norm + theta_edge / 2
    wt_circ = theta_edge / 2 * wt_norm

    #body integral goes from theta_edge to pi/2, b = pi/2, a = theta_edge
    theta_body = (np.pi / 2 - theta_edge) / 2 * x_norm + (np.pi / 2 +
                                                          theta_edge) / 2
    wt_body = (np.pi / 2 - theta_edge) / 2 * wt_norm

    #merge the circ and body lists into a single map
    theta_map = np.concatenate((theta_circ, theta_body), axis=0)
    weight_map = np.concatenate((wt_circ, wt_body), axis=0)

    #identify indices corresponding to the circular end caps and rectangular body
    circ_idx = np.arange(0, Ntheta)
    body_idx = np.arange(Ntheta, 2 * Ntheta)

    #k vectors of the light in medium (ki) and in scatterer (ks)
    ki = 2 * np.pi * n0 / wavelength
    ks = 2 * np.pi * ns / wavelength

    #precompute trig functions
    ct = np.cos(theta_map)
    st = np.sin(theta_map)
    #normal vector for circular surface (circ) requires tangent
    tant = np.tan(theta_map[circ_idx])
    #normal vector for rectangular surface (body) requires cotangent
    cott = 1 / np.tan(theta_map[body_idx])

    #precompute spherical angular polynomials
    [p_lm, pi_lm, tau_lm] = sf.legendre_normalized(ct, st, lmax)

    #radial coordinate of the surface, and the derivatives with respect to a and h
    #r_c: radial coordinate of circular end cap
    #r_b: radial coordinate of rectangular body
    r_c = h / 2 / ct[circ_idx]
    dr_c = r_c / h
    r_b = a / st[body_idx]
    dr_b = r_b / a

    #merge radial coordiantes into a single vector
    r = np.concatenate((r_c, r_b), axis=0)

    #derivatives of the integration limits for performing derivatives
    da_edge = 2 * h / (h**2 + 4 * a**2)
    dh_edge = -2 * a / (h**2 + 4 * a**2)

    #loop through each individual element of the J11, J12, J21, J22 matrices
    for li in np.arange(1, lmax + 1):

        #precompute bessel functiosn and derivatives
        b_li = bf.sph_bessel(nu, li, ki * r)
        db_li = bf.d1Z_Z_sph_bessel(nu, li, ki * r)
        db2_li = bf.d2Z_Z_sph_bessel(nu, li, ki * r)
        d1b_li = bf.d1Z_sph_bessel(nu, li, ki * r)
        for lp in np.arange(1, lmax + 1):

            #precompute bessel functions and derivatives
            j_lp = bf.sph_bessel(1, lp, ks * r)
            dj_lp = bf.d1Z_Z_sph_bessel(1, lp, ks * r)
            dj2_lp = bf.d2Z_Z_sph_bessel(1, lp, ks * r)
            d1j_lp = bf.d1Z_sph_bessel(1, lp, ks * r)

            #compute normalization factor
            lfactor = 1 / np.sqrt(li * (li + 1) * lp * (lp + 1))

            for mi in np.arange(-li, li + 1):

                #compute row index where element is placed
                n_i = compute_n(lmax, 1, li, mi) - 1

                #precompute spherical harmonic functions
                p_limi = p_lm[li][abs(mi)]
                pi_limi = pi_lm[li][abs(mi)]
                tau_limi = tau_lm[li][abs(mi)]

                for mp in np.arange(-lp, lp + 1):

                    #compute col index where element is placed
                    n_p = compute_n(lmax, 1, lp, mp) - 1

                    #precompute spherical harmonic functions
                    p_lpmp = p_lm[lp][abs(mp)]
                    pi_lpmp = pi_lm[lp][abs(mp)]
                    tau_lpmp = tau_lm[lp][abs(mp)]

                    #compute selection rules that includes symmetries
                    sr_1122 = selection_rules(li, mi, lp, mp, 1)
                    sr_1221 = selection_rules(li, mi, lp, mp, 2)

                    #perform integral about phi analytically. This is roughly a sinc function
                    if mi == mp:
                        phi_exp = np.pi
                    else:
                        phi_exp = -1j * (np.exp(1j * (mp - mi) * np.pi) -
                                         1) / (mp - mi)

                    #for J11 and J22 integrals
                    if sr_1122 != 0:
                        prefactor = sr_1122 * lfactor * phi_exp
                        ang = mp * pi_lpmp * tau_limi + mi * pi_limi * tau_lpmp
                        J11_r = -1j * weight_map * prefactor * r**2 * st * j_lp * b_li * ang
                        J11[n_i, n_p] = np.sum(J11_r)
                        dJ11dr = 2 * r * j_lp * b_li + r**2 * (
                            ks * d1j_lp * b_li + ki * d1b_li * j_lp)
                        dJ11[n_i, n_p, 0] = np.sum(
                            -1j * prefactor * weight_map[body_idx] *
                            st[body_idx] * dJ11dr[body_idx] * ang[body_idx] *
                            dr_b)
                        dJ11[n_i, n_p, 1] = np.sum(
                            -1j * prefactor * weight_map[circ_idx] *
                            st[circ_idx] * dJ11dr[circ_idx] * ang[circ_idx] *
                            dr_c)
                        J22_r = -1j * prefactor * weight_map * st / ki / ks * dj_lp * db_li * ang
                        J22_db = lp * (lp + 1) * mi * pi_limi * p_lpmp
                        J22_dj = li * (li + 1) * mp * pi_lpmp * p_limi
                        J22_t = -1j * prefactor * weight_map * st / ki / ks * (
                            J22_db * j_lp * db_li + J22_dj * b_li * dj_lp)
                        J22[n_i, n_p] = sum(J22_r) + sum(
                            J22_t[circ_idx] * tant) + sum(
                                J22_t[body_idx] * -cott)
                        dJ22edge = st[Ntheta] * (
                            J22_db[Ntheta] * j_lp[Ntheta] * db_li[Ntheta] +
                            J22_dj[Ntheta] * dj_lp[Ntheta] * b_li[Ntheta]
                        ) * (st[Ntheta] / ct[Ntheta] + ct[Ntheta] / st[Ntheta])
                        dJ22da1 = -1j / ki / ks * (
                            ks * dj2_lp[body_idx] * db_li[body_idx] +
                            ki * db2_li[body_idx] * dj_lp[body_idx]
                        ) * dr_b * st[body_idx] * ang[body_idx]
                        dJ22da2 = 1j / ki / ks * cott * st[body_idx] * dr_b * (
                            J22_db[body_idx] *
                            (ks * d1j_lp[body_idx] * db_li[body_idx] +
                             ki * j_lp[body_idx] * db2_li[body_idx]) +
                            J22_dj[body_idx] *
                            (ki * d1b_li[body_idx] * dj_lp[body_idx] +
                             ks * dj2_lp[body_idx] * b_li[body_idx]))
                        dJ22dh1 = -1j / ki / ks * (
                            ks * dj2_lp[circ_idx] * db_li[circ_idx] +
                            ki * db2_li[circ_idx] * dj_lp[circ_idx]
                        ) * dr_c * st[circ_idx] * ang[circ_idx]
                        dJ22dh2 = -1j / ki / ks * tant * st[circ_idx] * dr_c * (
                            J22_db[circ_idx] *
                            (ks * d1j_lp[circ_idx] * db_li[circ_idx] +
                             ki * j_lp[circ_idx] * db2_li[circ_idx]) +
                            J22_dj[circ_idx] *
                            (ki * d1b_li[circ_idx] * dj_lp[circ_idx] +
                             ks * dj2_lp[circ_idx] * b_li[circ_idx]))
                        dJ22[n_i, n_p, 0] = np.sum(
                            prefactor * weight_map[body_idx] *
                            dJ22da1) + np.sum(
                                prefactor * weight_map[body_idx] *
                                dJ22da2) + prefactor * dJ22edge * da_edge
                        dJ22[n_i, n_p, 1] = np.sum(
                            prefactor * weight_map[circ_idx] *
                            dJ22dh1) + np.sum(
                                prefactor * weight_map[circ_idx] *
                                dJ22dh2) + prefactor * dJ22edge * dh_edge
                    #for J12 and J21 integrals
                    if sr_1221 != 0:
                        prefactor = sr_1221 * lfactor * phi_exp
                        ang = mi * mp * pi_limi * pi_lpmp + tau_limi * tau_lpmp
                        J12_r = prefactor * weight_map / ki * r * st * j_lp * db_li * ang
                        J12_t = prefactor * weight_map / ki * r * st * li * (
                            li + 1) * j_lp * b_li * p_limi * tau_lpmp
                        J12[n_i, n_p] = np.sum(J12_r) + np.sum(
                            J12_t[circ_idx] * tant) + np.sum(
                                J12_t[body_idx] * -cott)
                        dJ12edge = li * (
                            li + 1
                        ) / ki / r[Ntheta] * st[Ntheta] * j_lp[Ntheta] * b_li[
                            Ntheta] * tau_lpmp[Ntheta] * p_limi[Ntheta] * (
                                st[Ntheta] / ct[Ntheta] +
                                ct[Ntheta] / st[Ntheta])
                        dJ12da1 = dr_b / ki * (
                            j_lp[body_idx] * db_li[body_idx] + r_b *
                            (ks * d1j_lp[body_idx] * b_li[body_idx] +
                             ki * j_lp[body_idx] * d1b_li[body_idx])
                        ) * st[body_idx] * ang[body_idx]
                        dJ12da2 = -li * (li + 1) / ki * dr_b * (
                            j_lp[body_idx] * b_li[body_idx] + r_b *
                            (ks * d1j_lp[body_idx] * b_li[body_idx] +
                             ki * j_lp[body_idx] * d1b_li[body_idx])
                        ) * cott * st[body_idx] * tau_lpmp[body_idx] * p_limi[
                            body_idx]
                        dJ12dh1 = dr_c / ki * (
                            j_lp[circ_idx] * db_li[circ_idx] + r_c *
                            (ks * d1j_lp[circ_idx] * b_li[circ_idx] +
                             ki * j_lp[circ_idx] * d1b_li[circ_idx])
                        ) * st[circ_idx] * ang[circ_idx]
                        dJ12dh2 = li * (li + 1) / ki * dr_c * (
                            j_lp[circ_idx] * b_li[circ_idx] + r_c *
                            (ks * d1j_lp[circ_idx] * b_li[circ_idx] +
                             ki * j_lp[circ_idx] * d1b_li[circ_idx])
                        ) * tant * st[circ_idx] * tau_lpmp[circ_idx] * p_limi[
                            circ_idx]
                        dJ12[n_i, n_p, 0] = np.sum(
                            prefactor * weight_map[body_idx] *
                            dJ12da1) + np.sum(
                                prefactor * weight_map[body_idx] *
                                dJ12da2) + prefactor * dJ12edge * da_edge
                        dJ12[n_i, n_p, 1] = np.sum(
                            prefactor * weight_map[circ_idx] *
                            dJ12dh1) + np.sum(
                                prefactor * weight_map[body_idx] *
                                dJ12da2) + prefactor * dJ12edge * dh_edge
                        J21_r = -prefactor * weight_map / ks * r * st * dj_lp * b_li * ang
                        J21_t = -prefactor * weight_map / ks * r * st * lp * (
                            lp + 1) * j_lp * b_li * p_lpmp * tau_limi
                        J21[n_i, n_p] = np.sum(J21_r) + np.sum(
                            J21_t[circ_idx] * tant) + np.sum(
                                J21_t[body_idx] * -cott)
                        dJ21edge = -lp * (
                            lp + 1
                        ) / ks / r[Ntheta] * st[Ntheta] * j_lp[Ntheta] * b_li[
                            Ntheta] * tau_lpmp[Ntheta] * p_limi[Ntheta] * (
                                st[Ntheta] / ct[Ntheta] +
                                ct[Ntheta] / st[Ntheta])
                        dJ21da1 = -dr_b / ks * (
                            b_li[body_idx] * dj_lp[body_idx] + r_b *
                            (ki * d1b_li[body_idx] * dj_lp[body_idx] +
                             ks * dj2_lp[body_idx] * b_li[body_idx])
                        ) * st[body_idx] * ang[body_idx]
                        dJ21da2 = lp * (lp + 1) / ks * dr_b * (
                            j_lp[body_idx] * b_li[body_idx] + r_b *
                            (ks * d1j_lp[body_idx] * b_li[body_idx] +
                             ki * d1b_li[body_idx] * j_lp[body_idx])
                        ) * cott * st[body_idx] * tau_limi[body_idx] * p_lpmp[
                            body_idx]
                        dJ21dh1 = -dr_c / ks * (
                            b_li[circ_idx] * dj_lp[circ_idx] + r_c *
                            (ki * d1b_li[circ_idx] * dj_lp[circ_idx] +
                             ks * dj2_lp[circ_idx] * b_li[circ_idx])
                        ) * st[circ_idx] * ang[circ_idx]
                        dJ21dh2 = -lp * (lp + 1) / ks * dr_c * (
                            j_lp[circ_idx] * b_li[circ_idx] + r_c *
                            (ks * d1j_lp[circ_idx] * b_li[circ_idx] +
                             ki * d1b_li[circ_idx] * j_lp[circ_idx])
                        ) * tant * st[circ_idx] * tau_limi[circ_idx] * p_lpmp[
                            circ_idx]
                        dJ21[n_i, n_p, 0] = np.sum(
                            prefactor * weight_map[body_idx] *
                            dJ21da1) + np.sum(
                                prefactor * weight_map[body_idx] *
                                dJ21da2) + prefactor * dJ21edge * da_edge
                        dJ21[n_i, n_p, 1] = np.sum(
                            prefactor * weight_map[circ_idx] *
                            dJ21dh1) + np.sum(
                                prefactor * weight_map[circ_idx] *
                                dJ21dh2) + prefactor * dJ21edge * dh_edge

    return J11, J12, J21, J22, dJ11, dJ12, dJ21, dJ22
def spherical_vector_wave_function(x, y, z, k, nu, tau, l, m):
    """Electric field components of spherical vector wave function (SVWF). The conventions are chosen according to
    `A. Doicu, T. Wriedt, and Y. A. Eremin: "Light Scattering by Systems of Particles", Springer-Verlag, 2006
    <https://doi.org/10.1007/978-3-540-33697-6>`_

    Args:
        x (numpy.ndarray):      x-coordinate of position where to test the field (length unit)
        y (numpy.ndarray):      y-coordinate of position where to test the field
        z (numpy.ndarray):      z-coordinate of position where to test the field
        k (float or complex):   wavenumber (inverse length unit)
        nu (int):               1 for regular waves, 3 for outgoing waves
        tau (int):              spherical polarization, 0 for spherical TE and 1 for spherical TM
        l (int):                l=1,... multipole degree (polar quantum number)
        m (int):                m=-l,...,l multipole order (azimuthal quantum number)

    Returns:
        - x-coordinate of SVWF electric field (numpy.ndarray)
        - y-coordinate of SVWF electric field (numpy.ndarray)
        - z-coordinate of SVWF electric field (numpy.ndarray)
    """
    x = np.array(x)
    y = np.array(y)
    z = np.array(z)

    r = np.sqrt(x**2 + y**2 + z**2)
    non0 = np.logical_not(r == 0)

    theta = np.zeros(x.shape)
    theta[non0] = np.arccos(z[non0] / r[non0])
    phi = np.arctan2(y, x)

    # unit vector in r-direction

    er_x = np.zeros(x.shape)
    er_y = np.zeros(x.shape)
    er_z = np.ones(x.shape)

    er_x[non0] = x[non0] / r[non0]
    er_y[non0] = y[non0] / r[non0]
    er_z[non0] = z[non0] / r[non0]

    # unit vector in theta-direction
    eth_x = np.cos(theta) * np.cos(phi)
    eth_y = np.cos(theta) * np.sin(phi)
    eth_z = -np.sin(theta)

    # unit vector in phi-direction
    eph_x = -np.sin(phi)
    eph_y = np.cos(phi)
    eph_z = x - x

    cos_thet = np.cos(theta)
    sin_thet = np.sin(theta)
    plm_list, pilm_list, taulm_list = sf.legendre_normalized(
        cos_thet, sin_thet, l)
    plm = plm_list[l][abs(m)]
    pilm = pilm_list[l][abs(m)]
    taulm = taulm_list[l][abs(m)]

    kr = k * r
    if nu == 1:
        bes = sf.spherical_bessel(l, kr)
        dxxz = sf.dx_xj(l, kr)
        bes_kr = np.zeros(kr.shape)
        dxxz_kr = np.zeros(kr.shape)
        zero = (r == 0)
        nonzero = np.logical_not(zero)
        bes_kr[zero] = (l == 1) / 3
        dxxz_kr[zero] = (l == 1) * 2 / 3
        bes_kr[nonzero] = (bes[nonzero] / kr[nonzero])
        dxxz_kr[nonzero] = (dxxz[nonzero] / kr[nonzero])
    elif nu == 3:
        bes = sf.spherical_hankel(l, kr)
        dxxz = sf.dx_xh(l, kr)
        bes_kr = bes / kr
        dxxz_kr = dxxz / kr
    else:
        raise ValueError('nu must be 1 (regular SVWF) or 3 (outgoing SVWF)')

    eimphi = np.exp(1j * m * phi)
    prefac = 1 / np.sqrt(2 * l * (l + 1))
    if tau == 0:
        Ex = prefac * bes * (1j * m * pilm * eth_x - taulm * eph_x) * eimphi
        Ey = prefac * bes * (1j * m * pilm * eth_y - taulm * eph_y) * eimphi
        Ez = prefac * bes * (1j * m * pilm * eth_z - taulm * eph_z) * eimphi
    elif tau == 1:
        Ex = prefac * (l * (l + 1) * bes_kr * plm * er_x + dxxz_kr *
                       (taulm * eth_x + 1j * m * pilm * eph_x)) * eimphi
        Ey = prefac * (l * (l + 1) * bes_kr * plm * er_y + dxxz_kr *
                       (taulm * eth_y + 1j * m * pilm * eph_y)) * eimphi
        Ez = prefac * (l * (l + 1) * bes_kr * plm * er_z + dxxz_kr *
                       (taulm * eth_z + 1j * m * pilm * eph_z)) * eimphi
    else:
        raise ValueError('tau must be 0 (spherical TE) or 1 (spherical TM)')

    return Ex, Ey, Ez