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
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
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