def block_rotation_matrix_D_svwf(l_max, m_max, alpha, beta, gamma, wdsympy=False): """Rotation matrix for the rotation of SVWFs between the labratory coordinate system (L) and a rotated coordinate system (R) Args: l_max (int): Maximal multipole degree m_max (int): Maximal multipole order alpha (float): First Euler angle, rotation around z-axis, in rad beta (float): Second Euler angle, rotation around y'-axis in rad gamma (float): Third Euler angle, rotation around z''-axis in rad wdsympy (bool): If True, Wigner-d-functions come from the sympy toolbox Returns: rotation matrix of dimension [blocksize, blocksize] """ b_size = flds.blocksize(l_max, m_max) rotation_matrix = np.zeros([b_size, b_size], dtype=complex) for l in range(l_max + 1): mstop = min(l, m_max) for m1 in range(-mstop, mstop + 1): for m2 in range(-mstop, mstop + 1): rotation_matrix_coefficient = mathfunc.wigner_D(l, m1, m2, alpha, beta, gamma, wdsympy) for tau in range(2): n1 = flds.multi_to_single_index(tau, l, m1, l_max, m_max) n2 = flds.multi_to_single_index(tau, l, m2, l_max, m_max) rotation_matrix[n1, n2] = rotation_matrix_coefficient return rotation_matrix
def relative_difference_Tmatrices(Tmat_s, lmax_s, mmax_s, Tmat_l, lmax_l, mmax_l): n_list_s = np.zeros(len(Tmat_s), dtype=int) n_list_l = np.zeros(len(Tmat_s), dtype=int) idx = 0 for tau in range(2): for l in range(1, lmax_s + 1): for m in range(np.max([-l, -mmax_s]), np.min([l, mmax_s]) + 1): n_list_s[idx] = flds.multi_to_single_index( tau, l, m, lmax_s, mmax_s) n_list_l[idx] = flds.multi_to_single_index( tau, l, m, lmax_l, mmax_l) idx += 1 row, column = np.meshgrid(n_list_s, n_list_s) row2, column2 = np.meshgrid(n_list_l, n_list_l) TMat_temp = np.zeros([len(Tmat_l), len(Tmat_l)], dtype=complex) TMat_temp[row2, column2] = Tmat_s[row, column] return np.linalg.norm(Tmat_l - TMat_temp) / np.linalg.norm(Tmat_l)
def pwe_to_swe_conversion(pwe, l_max, m_max, reference_point): """Convert plane wave expansion object to a spherical wave expansion object. Args: pwe (PlaneWaveExpansion): Plane wave expansion to be converted l_max (int): Maximal multipole degree of spherical wave expansion m_max (int): Maximal multipole order of spherical wave expansion reference_point (list): Coordinates of reference point in the format [x, y, z] Returns: SphericalWaveExpansion object. """ if reference_point[2] < pwe.lower_z or reference_point[2] > pwe.upper_z: raise ValueError('reference point not inside domain of pwe validity') swe = fex.SphericalWaveExpansion(k=pwe.k, l_max=l_max, m_max=m_max, kind='regular', reference_point=reference_point, lower_z=pwe.lower_z, upper_z=pwe.upper_z) kpgrid = pwe.k_parallel_grid() agrid = pwe.azimuthal_angle_grid() kx = kpgrid * np.cos(agrid) ky = kpgrid * np.sin(agrid) kz = pwe.k_z_grid() kzvec = pwe.k_z() kvec = np.array([kx, ky, kz]) rswe_mn_rpwe = np.array(reference_point) - np.array(pwe.reference_point) # phase factor for the translation of the reference point from rvec_iS to rvec_S ejkriSS = np.exp(1j * (kvec[0] * rswe_mn_rpwe[0] + kvec[1] * rswe_mn_rpwe[1] + kvec[2] * rswe_mn_rpwe[2])) # phase factor times pwe coefficients gejkriSS = pwe.coefficients * ejkriSS[None, :, :] # indices: pol, jk, ja ct = kzvec / pwe.k st = pwe.k_parallel / pwe.k plm_list, pilm_list, taulm_list = mathfunc.legendre_normalized(ct, st, l_max) for m in range(-m_max, m_max + 1): emjma_geijkriSS = np.exp(-1j * m * pwe.azimuthal_angles)[None, None, :] * gejkriSS for l in range(max(1, abs(m)), l_max + 1): for tau in range(2): ak_integrand = np.zeros(kpgrid.shape, dtype=complex) for pol in range(2): Bdag = transformation_coefficients_vwf(tau, l, m, pol=pol, pilm_list=pilm_list, taulm_list=taulm_list, kz=kzvec, dagger=True) ak_integrand += Bdag[:, None] * emjma_geijkriSS[pol, :, :] if len(pwe.k_parallel) > 1: an = np.trapz(np.trapz(ak_integrand, pwe.azimuthal_angles) * pwe.k_parallel, pwe.k_parallel) * 4 else: an = ak_integrand * 4 swe.coefficients[flds.multi_to_single_index(tau, l, m, swe.l_max, swe.m_max)] = np.squeeze(an) return swe
def testMulti2SingleSTLM(self): idcs = [] lmax = 5 mmax = 5 count = 0 for tau in range(2): for l in range(1, lmax + 1): for m in range(-l, l + 1): idcs.append( flds.multi_to_single_index(tau=tau, l=l, m=m, l_max=lmax, m_max=mmax)) count += 1 self.assertEqual(idcs, list(range(len(idcs)))) ind_num = flds.blocksize(lmax, mmax) self.assertEqual(count, ind_num) idcs = [] lmax = 6 mmax = 3 count = 0 for tau in range(2): for l in range(1, lmax + 1): mlim = min(l, mmax) for m in range(-mlim, mlim + 1): idcs.append( flds.multi_to_single_index(tau=tau, l=l, m=m, l_max=lmax, m_max=mmax)) count += 1 self.assertEqual(idcs, list(range(len(idcs)))) ind_num = flds.blocksize(lmax, mmax) self.assertEqual(count, ind_num)
def testTmatrix(self): t = tmt.t_matrix_sphere(omega * n_medium, omega * n_particle, radius, lmax, mmax) n = flds.multi_to_single_index(tau, l, m, lmax, mmax) mie = tmt.mie_coefficient(tau, l, omega * n_medium, omega * n_particle, radius) assert t[n, n] == mie sphere1 = smuthi.particles.Sphere(radius=100, refractive_index=3, position=[100, 200, 300], l_max=lmax, m_max=mmax) sphere2 = smuthi.particles.Sphere(radius=100, refractive_index=3, position=[200, -200, 200], l_max=lmax, m_max=mmax) sphere3 = smuthi.particles.Sphere(radius=200, refractive_index=2+0.2j, position=[200,-200,200], l_max=lmax, m_max=mmax) t2 = tmt.t_matrix(vacuum_wavelength=550, n_medium=n_medium, particle=sphere1) t3 = tmt.t_matrix_sphere(2*np.pi/550 * n_medium, 2*np.pi/550 * 3, 100, lmax, mmax) np.testing.assert_allclose(t2, t3)
def index(self, i, tau, l, m): r""" Args: i (int): particle number tau (int): spherical polarization index l (int): multipole degree m (int): multipole order Returns: Position in a system vector that corresponds to the :math:`(\tau, l, m)` coefficient of the i-th particle. """ blocksizes = [ flds.blocksize(particle.l_max, particle.m_max) for particle in self.particle_list[:i] ] return sum(blocksizes) + flds.multi_to_single_index( tau, l, m, self.particle_list[i].l_max, self.particle_list[i].m_max)
def internal_field_piecewise_expansion(vacuum_wavelength, particle_list, layer_system): """Compute a piecewise field expansion of the internal field of spheres. Args: vacuum_wavelength (float): vacuum wavelength particle_list (list): list of smuthi.particles.Particle objects layer_system (smuthi.layers.LayerSystem): stratified medium Returns: internal field as smuthi.field_expansion.PiecewiseFieldExpansion object """ intfld = fldex.PiecewiseFieldExpansion() for particle in particle_list: if type(particle).__name__ == 'Sphere': i_part = layer_system.layer_number(particle.position[2]) k_medium = flds.angular_frequency(vacuum_wavelength) * layer_system.refractive_indices[i_part] k_particle = flds.angular_frequency(vacuum_wavelength) * particle.refractive_index internal_field = fldex.SphericalWaveExpansion( k_particle, l_max=particle.l_max, m_max=particle.m_max, kind='regular', reference_point=particle.position) internal_field.validity_conditions.append(particle.is_inside) for tau in range(2): for l in range(1, particle.l_max+1): for m in range(-l, l+1): n = flds.multi_to_single_index(tau, l, m, particle.l_max, particle.m_max) b_to_c = (tmt.internal_mie_coefficient(tau, l, k_medium, k_particle, particle.radius) / tmt.mie_coefficient(tau, l, k_medium, k_particle, particle.radius)) internal_field.coefficients[n] = particle.scattered_field.coefficients[n] * b_to_c intfld.expansion_list.append(internal_field) particle.internal_field = internal_field return intfld
def t_matrix_sphere(k_medium, k_particle, radius, l_max, m_max): """T-matrix of a spherical scattering object. Args: k_medium (float or complex): Wavenumber in surrounding medium (inverse length unit) k_particle (float or complex): Wavenumber inside sphere (inverse length unit) radius (float): Radius of sphere (length unit) l_max (int): Maximal multipole degree m_max (int): Maximal multipole order Returns: T-matrix as ndarray """ t = np.zeros((flds.blocksize(l_max, m_max), flds.blocksize(l_max, m_max)), dtype=complex) for tau in range(2): for m in range(-m_max, m_max + 1): for l in range(max(1, abs(m)), l_max + 1): n = flds.multi_to_single_index(tau, l, m, l_max, m_max) t[n, n] = mie_coefficient(tau, l, k_medium, k_particle, radius) return t
def __init__(self, vacuum_wavelength, particle_list, layer_system, k_parallel='default', resolution=None, interpolator_kind='linear'): z_list = [particle.position[2] for particle in particle_list] assert z_list.count(z_list[0]) == len(z_list) CouplingMatrixRadialLookup.__init__(self, vacuum_wavelength, particle_list, layer_system, k_parallel, resolution) x_array = np.array( [particle.position[0] for particle in particle_list]) y_array = np.array( [particle.position[1] for particle in particle_list]) self.particle_rho_array = np.sqrt( (x_array[:, None] - x_array[None, :])**2 + (y_array[:, None] - y_array[None, :])**2) self.particle_phi_array = np.arctan2( y_array[:, None] - y_array[None, :], x_array[:, None] - x_array[None, :]) # contains for each n all positions in the large system arrays that correspond to n: self.system_vector_index_list = [[] for i in range(self.blocksize)] # same size as system_vector_index_list, contains the according particle numbers: self.particle_number_list = [[] for i in range(self.blocksize)] self.m_list = [None for i in range(self.blocksize)] for i, particle in enumerate(particle_list): for m in range(-particle.m_max, particle.m_max + 1): for l in range(max(1, abs(m)), particle.l_max + 1): for tau in range(2): n_lookup = flds.multi_to_single_index(tau=tau, l=l, m=m, l_max=self.l_max, m_max=self.m_max) self.system_vector_index_list[n_lookup].append( self.index(i, tau, l, m)) self.particle_number_list[n_lookup].append(i) self.m_list[n_lookup] = m for n in range(self.blocksize): self.system_vector_index_list[n] = np.array( self.system_vector_index_list[n]) self.particle_number_list[n] = np.array( self.particle_number_list[n]) lookup = [[None for i in range(self.blocksize)] for i2 in range(self.blocksize)] for n1 in range(self.blocksize): for n2 in range(self.blocksize): lookup[n1][n2] = scipy.interpolate.interp1d( x=self.radial_distance_array, y=self.lookup_table[:, n1, n2], kind=interpolator_kind, axis=-1, assume_sorted=True) def matvec(in_vec): out_vec = np.zeros(shape=in_vec.shape, dtype=complex) for n1 in range(self.blocksize): i1 = self.particle_number_list[n1] idx1 = self.system_vector_index_list[n1] m1 = self.m_list[n1] for n2 in range(self.blocksize): i2 = self.particle_number_list[n2] idx2 = self.system_vector_index_list[n2] m2 = self.m_list[n2] rho = self.particle_rho_array[i1[:, None], i2[None, :]] phi = self.particle_phi_array[i1[:, None], i2[None, :]] M = lookup[n1][n2](rho) M = M * np.exp(1j * (m2 - m1) * phi) out_vec[idx1] += M.dot(in_vec[idx2]) return out_vec self.linear_operator = scipy.sparse.linalg.LinearOperator( shape=self.shape, matvec=matvec, dtype=complex)
def __init__(self, vacuum_wavelength, particle_list, layer_system, k_parallel='default', resolution=None, cuda_blocksize=None, interpolator_kind='linear'): if cuda_blocksize is None: cuda_blocksize = cu.default_blocksize CouplingMatrixRadialLookup.__init__(self, vacuum_wavelength, particle_list, layer_system, k_parallel, resolution) sys.stdout.write('Prepare CUDA kernel and device lookup data ... ') sys.stdout.flush() start_time = time.time() if interpolator_kind == 'linear': coupling_source = cusrc.linear_radial_lookup_source % ( self.blocksize, self.shape[0], self.radial_distance_array.min(), resolution) elif interpolator_kind == 'cubic': coupling_source = cusrc.cubic_radial_lookup_source % ( self.blocksize, self.shape[0], self.radial_distance_array.min(), resolution) coupling_function = cu.SourceModule(coupling_source).get_function( "coupling_kernel") n_lookup_array = np.zeros(self.shape[0], dtype=np.uint32) m_particle_array = np.zeros(self.shape[0], dtype=np.float32) x_array = np.zeros(self.shape[0], dtype=np.float32) y_array = np.zeros(self.shape[0], dtype=np.float32) i_particle = 0 for i, particle in enumerate(particle_list): for m in range(-particle.m_max, particle.m_max + 1): for l in range(max(1, abs(m)), particle.l_max + 1): for tau in range(2): # idx = self.index(i, tau, l, m) i_taulm = flds.multi_to_single_index( tau, l, m, particle.l_max, particle.m_max) idx = i_particle + i_taulm n_lookup_array[idx] = flds.multi_to_single_index( tau, l, m, self.l_max, self.m_max) m_particle_array[idx] = m # scale the x and y position to the lookup resolution: x_array[idx] = particle.position[0] y_array[idx] = particle.position[1] i_particle += flds.blocksize(particle.l_max, particle.m_max) # lookup as numpy array in required shape re_lookup = self.lookup_table.real.astype(np.float32) im_lookup = self.lookup_table.imag.astype(np.float32) # transfer data to gpu n_lookup_array_d = cu.gpuarray.to_gpu(n_lookup_array) m_particle_array_d = cu.gpuarray.to_gpu(m_particle_array) x_array_d = cu.gpuarray.to_gpu(x_array) y_array_d = cu.gpuarray.to_gpu(y_array) re_lookup_d = cu.gpuarray.to_gpu(re_lookup) im_lookup_d = cu.gpuarray.to_gpu(im_lookup) sys.stdout.write('done | elapsed: ' + str(int(time.time() - start_time)) + 's\n') sys.stdout.flush() cuda_gridsize = (self.shape[0] + cuda_blocksize - 1) // cuda_blocksize def matvec(in_vec): re_in_vec_d = cu.gpuarray.to_gpu(np.float32(in_vec.real)) im_in_vec_d = cu.gpuarray.to_gpu(np.float32(in_vec.imag)) re_result_d = cu.gpuarray.zeros(in_vec.shape, dtype=np.float32) im_result_d = cu.gpuarray.zeros(in_vec.shape, dtype=np.float32) coupling_function(n_lookup_array_d.gpudata, m_particle_array_d.gpudata, x_array_d.gpudata, y_array_d.gpudata, re_lookup_d.gpudata, im_lookup_d.gpudata, re_in_vec_d.gpudata, im_in_vec_d.gpudata, re_result_d.gpudata, im_result_d.gpudata, block=(cuda_blocksize, 1, 1), grid=(cuda_gridsize, 1)) return re_result_d.get() + 1j * im_result_d.get() self.linear_operator = scipy.sparse.linalg.LinearOperator( shape=self.shape, matvec=matvec, dtype=complex)
def __init__(self, vacuum_wavelength, particle_list, layer_system, k_parallel='default', resolution=None, interpolator_kind='cubic'): if interpolator_kind == 'cubic': interpolation_order = 3 else: interpolation_order = 1 CouplingMatrixVolumeLookup.__init__(self, vacuum_wavelength, particle_list, layer_system, k_parallel, resolution) x_array = np.array( [particle.position[0] for particle in particle_list]) y_array = np.array( [particle.position[1] for particle in particle_list]) z_array = np.array( [particle.position[2] for particle in particle_list]) self.particle_rho_array = np.sqrt( (x_array[:, None] - x_array[None, :])**2 + (y_array[:, None] - y_array[None, :])**2) self.particle_phi_array = np.arctan2( y_array[:, None] - y_array[None, :], x_array[:, None] - x_array[None, :]) self.particle_sz_array = z_array[:, None] + z_array[None, :] self.particle_dz_array = z_array[:, None] - z_array[None, :] # contains for each n all positions in the large system arrays that correspond to n: self.system_vector_index_list = [[] for i in range(self.blocksize)] # same size as system_vector_index_list, contains the according particle numbers: self.particle_number_list = [[] for i in range(self.blocksize)] self.m_list = [None for i in range(self.blocksize)] for i, particle in enumerate(particle_list): for m in range(-particle.m_max, particle.m_max + 1): for l in range(max(1, abs(m)), particle.l_max + 1): for tau in range(2): n_lookup = flds.multi_to_single_index(tau=tau, l=l, m=m, l_max=self.l_max, m_max=self.m_max) self.system_vector_index_list[n_lookup].append( self.index(i, tau, l, m)) self.particle_number_list[n_lookup].append(i) self.m_list[n_lookup] = m for n in range(self.blocksize): self.system_vector_index_list[n] = np.array( self.system_vector_index_list[n]) self.particle_number_list[n] = np.array( self.particle_number_list[n]) self.lookup_plus_real = [[None for i in range(self.blocksize)] for i2 in range(self.blocksize)] self.lookup_plus_imag = [[None for i in range(self.blocksize)] for i2 in range(self.blocksize)] self.lookup_minus_real = [[None for i in range(self.blocksize)] for i2 in range(self.blocksize)] self.lookup_minus_imag = [[None for i in range(self.blocksize)] for i2 in range(self.blocksize)] for n1 in range(self.blocksize): for n2 in range(self.blocksize): self.lookup_plus_real[n1][ n2] = scipy.interpolate.RectBivariateSpline( x=self.rho_array, y=self.sum_z_array, z=self.lookup_table_plus[:, :, n1, n2].real, kx=interpolation_order, ky=interpolation_order) self.lookup_plus_imag[n1][ n2] = scipy.interpolate.RectBivariateSpline( x=self.rho_array, y=self.sum_z_array, z=self.lookup_table_plus[:, :, n1, n2].imag, kx=interpolation_order, ky=interpolation_order) self.lookup_minus_real[n1][ n2] = scipy.interpolate.RectBivariateSpline( x=self.rho_array, y=self.diff_z_array, z=self.lookup_table_minus[:, :, n1, n2].real, kx=interpolation_order, ky=interpolation_order) self.lookup_minus_imag[n1][ n2] = scipy.interpolate.RectBivariateSpline( x=self.rho_array, y=self.diff_z_array, z=self.lookup_table_minus[:, :, n1, n2].imag, kx=interpolation_order, ky=interpolation_order) def matvec(in_vec): out_vec = np.zeros(shape=in_vec.shape, dtype=complex) for n1 in range(self.blocksize): i1 = self.particle_number_list[n1] idx1 = self.system_vector_index_list[n1] m1 = self.m_list[n1] for n2 in range(self.blocksize): i2 = self.particle_number_list[n2] idx2 = self.system_vector_index_list[n2] m2 = self.m_list[n2] rho = self.particle_rho_array[i1[:, None], i2[None, :]] phi = self.particle_phi_array[i1[:, None], i2[None, :]] sz = self.particle_sz_array[i1[:, None], i2[None, :]] dz = self.particle_dz_array[i1[:, None], i2[None, :]] Mpl = self.lookup_plus_real[n1][n2].ev( rho, sz) + 1j * self.lookup_plus_imag[n1][n2].ev(rho, sz) Mmn = self.lookup_minus_real[n1][n2].ev( rho, dz) + 1j * self.lookup_minus_imag[n1][n2].ev(rho, dz) M = (Mpl + Mmn) * np.exp(1j * (m2 - m1) * phi) out_vec[idx1] += M.dot(in_vec[idx2]) return out_vec self.linear_operator = scipy.sparse.linalg.LinearOperator( shape=self.shape, matvec=matvec, dtype=complex)
def taxsym_read_tmatrix(filename, l_max, m_max): """Export TAXSYM.f90 output to SMUTHI T-matrix. .. todo:: feedback to adapt particle m_max to nfmds m_max Args: filename (str): Name of the file containing the T-matrix output of TAXSYM.f90 l_max (int): Maximal multipole degree m_max (int): Maximal multipole order Returns: T-matrix as numpy.ndarray """ with open(nfmds.nfmds_folder + '/TMATFILES/Info' + filename, 'r') as info_file: info_file_lines = info_file.readlines() assert 'The scatterer is an axisymmetric particle' in ' '.join( info_file_lines) for line in info_file_lines: if line.split()[0:4] == ['-', 'maximum', 'expansion', 'order,']: n_rank = int(line.split()[-1][0:-1]) if line.split()[0:5] == ['-', 'number', 'of', 'azimuthal', 'modes,']: m_rank = int(line.split()[-1][0:-1]) with open(nfmds.nfmds_folder + '/TMATFILES/' + filename, 'r') as tmat_file: tmat_lines = tmat_file.readlines() t_nfmds = [[]] column_index = 0 for line in tmat_lines[3:]: split_line = line.split() for i_entry in range(int(len(split_line) / 2)): if column_index == 2 * n_rank: t_nfmds.append([]) column_index = 0 t_nfmds[-1].append( complex(split_line[2 * i_entry]) + 1j * complex(split_line[2 * i_entry + 1])) column_index += 1 t_matrix = np.zeros( (flds.blocksize(l_max, m_max), flds.blocksize(l_max, m_max)), dtype=complex) for m in range(-m_max, m_max + 1): n_max_nfmds = n_rank - max(1, abs(m)) + 1 for tau1 in range(2): for l1 in range(max(1, abs(m)), l_max + 1): n1 = flds.multi_to_single_index(tau=tau1, l=l1, m=m, l_max=l_max, m_max=m_max) l1_nfmds = l1 - max(1, abs(m)) n1_nfmds = 2 * n_rank * abs(m) + tau1 * n_max_nfmds + l1_nfmds for tau2 in range(2): for l2 in range(max(1, abs(m)), l_max + 1): n2 = flds.multi_to_single_index(tau=tau2, l=l2, m=m, l_max=l_max, m_max=m_max) l2_nfmds = l2 - max(1, abs(m)) n2_nfmds = tau2 * n_max_nfmds + l2_nfmds if abs(m) <= m_rank: if m >= 0: t_matrix[n1, n2] = t_nfmds[n1_nfmds][n2_nfmds] else: t_matrix[n1, n2] = t_nfmds[n1_nfmds][n2_nfmds] * ( -1)**(tau1 + tau2) return t_matrix
def direct_coupling_block_pvwf_mediated(vacuum_wavelength, receiving_particle, emitting_particle, layer_system, k_parallel): """Direct particle coupling matrix :math:`W` for two particles (via plane vector wave functions). For details, see: Dominik Theobald et al., Phys. Rev. A 96, 033822, DOI: 10.1103/PhysRevA.96.033822 or arXiv:1708.04808 Args: vacuum_wavelength (float): Vacuum wavelength :math:`\lambda` (length unit) receiving_particle (smuthi.particles.Particle): Particle that receives the scattered field emitting_particle (smuthi.particles.Particle): Particle that emits the scattered field layer_system (smuthi.layers.LayerSystem): Stratified medium in which the coupling takes place k_parallel (numpy.array): In-plane wavenumber for plane wave expansion Returns: Direct coupling matrix block (numpy array). """ if type(receiving_particle).__name__ != 'Spheroid' or type( emitting_particle).__name__ != 'Spheroid': raise NotImplementedError( 'plane wave coupling currently implemented only for spheroids') lmax1 = receiving_particle.l_max mmax1 = receiving_particle.m_max assert lmax1 == mmax1, 'PVWF coupling requires lmax == mmax for each particle.' lmax2 = emitting_particle.l_max mmax2 = emitting_particle.m_max assert lmax2 == mmax2, 'PVWF coupling requires lmax == mmax for each particle.' lmax = max([lmax1, lmax2]) m_max = max([mmax1, mmax2]) blocksize1 = flds.blocksize(lmax1, mmax1) blocksize2 = flds.blocksize(lmax2, mmax2) n_medium = layer_system.refractive_indices[layer_system.layer_number( receiving_particle.position[2])] # finding the orientation of a plane separating the spheroids _, _, alpha, beta = spheroids_closest_points( emitting_particle.semi_axis_a, emitting_particle.semi_axis_c, emitting_particle.position, emitting_particle.euler_angles, receiving_particle.semi_axis_a, receiving_particle.semi_axis_c, receiving_particle.position, receiving_particle.euler_angles) # positions r1 = np.array(receiving_particle.position) r2 = np.array(emitting_particle.position) r21_lab = r1 - r2 # laboratory coordinate system # distance vector in rotated coordinate system r21_rot = np.dot( np.dot([[np.cos(beta), 0, np.sin(beta)], [0, 1, 0], [-np.sin(beta), 0, np.cos(beta)]], [[np.cos(alpha), -np.sin(alpha), 0], [np.sin(alpha), np.cos(alpha), 0], [0, 0, 1]]), r21_lab) rho21 = (r21_rot[0]**2 + r21_rot[1]**2)**0.5 phi21 = np.arctan2(r21_rot[1], r21_rot[0]) z21 = r21_rot[2] # wavenumbers omega = flds.angular_frequency(vacuum_wavelength) k = omega * n_medium kz = flds.k_z(k_parallel=k_parallel, vacuum_wavelength=vacuum_wavelength, refractive_index=n_medium) if z21 < 0: kz_var = -kz else: kz_var = kz # Bessel lookup bessel_list = [] for dm in range(mmax1 + mmax2 + 1): bessel_list.append(scipy.special.jn(dm, k_parallel * rho21)) # legendre function lookups ct = kz_var / k st = k_parallel / k _, pilm_list, taulm_list = sf.legendre_normalized(ct, st, lmax) # initialize result w = np.zeros((blocksize1, blocksize2), dtype=complex) # prefactor const_arr = k_parallel / (kz * k) * np.exp(1j * (kz_var * z21)) for m1 in range(-mmax1, mmax1 + 1): for m2 in range(-mmax2, mmax2 + 1): jmm_eimphi_bessel = 4 * 1j**abs(m2 - m1) * np.exp( 1j * phi21 * (m2 - m1)) * bessel_list[abs(m2 - m1)] prefactor = const_arr * jmm_eimphi_bessel for l1 in range(max(1, abs(m1)), lmax1 + 1): for l2 in range(max(1, abs(m2)), lmax2 + 1): for tau1 in range(2): n1 = flds.multi_to_single_index( tau1, l1, m1, lmax1, mmax1) for tau2 in range(2): n2 = flds.multi_to_single_index( tau2, l2, m2, lmax2, mmax2) for pol in range(2): B_dag = trf.transformation_coefficients_vwf( tau1, l1, m1, pol, pilm_list=pilm_list, taulm_list=taulm_list, dagger=True) B = trf.transformation_coefficients_vwf( tau2, l2, m2, pol, pilm_list=pilm_list, taulm_list=taulm_list, dagger=False) integrand = prefactor * B * B_dag w[n1, n2] += np.trapz(integrand, k_parallel) rot_mat_1 = trf.block_rotation_matrix_D_svwf(lmax1, mmax1, 0, beta, alpha) rot_mat_2 = trf.block_rotation_matrix_D_svwf(lmax2, mmax2, -alpha, -beta, 0) return np.dot(np.dot(np.transpose(rot_mat_1), w), np.transpose(rot_mat_2))
def direct_coupling_block(vacuum_wavelength, receiving_particle, emitting_particle, layer_system): """Direct particle coupling matrix :math:`W` for two particles. This routine is explicit, but slow. Args: vacuum_wavelength (float): Vacuum wavelength :math:`\lambda` (length unit) receiving_particle (smuthi.particles.Particle): Particle that receives the scattered field emitting_particle (smuthi.particles.Particle): Particle that emits the scattered field layer_system (smuthi.layers.LayerSystem): Stratified medium in which the coupling takes place Returns: Direct coupling matrix block as numpy array. """ omega = flds.angular_frequency(vacuum_wavelength) # index specs lmax1 = receiving_particle.l_max mmax1 = receiving_particle.m_max lmax2 = emitting_particle.l_max mmax2 = emitting_particle.m_max blocksize1 = flds.blocksize(lmax1, mmax1) blocksize2 = flds.blocksize(lmax2, mmax2) # initialize result w = np.zeros((blocksize1, blocksize2), dtype=complex) # check if particles are in same layer rS1 = receiving_particle.position rS2 = emitting_particle.position iS1 = layer_system.layer_number(rS1[2]) iS2 = layer_system.layer_number(rS2[2]) if iS1 == iS2 and not emitting_particle == receiving_particle: k = omega * layer_system.refractive_indices[iS1] dx = rS1[0] - rS2[0] dy = rS1[1] - rS2[1] dz = rS1[2] - rS2[2] d = np.sqrt(dx**2 + dy**2 + dz**2) cos_theta = dz / d sin_theta = np.sqrt(dx**2 + dy**2) / d phi = np.arctan2(dy, dx) # spherical functions bessel_h = [ sf.spherical_hankel(n, k * d) for n in range(lmax1 + lmax2 + 1) ] legendre, _, _ = sf.legendre_normalized(cos_theta, sin_theta, lmax1 + lmax2) # the particle coupling operator is the transpose of the SVWF translation operator # therefore, (l1,m1) and (l2,m2) are interchanged: for m1 in range(-mmax1, mmax1 + 1): for m2 in range(-mmax2, mmax2 + 1): eimph = np.exp(1j * (m2 - m1) * phi) for l1 in range(max(1, abs(m1)), lmax1 + 1): for l2 in range(max(1, abs(m2)), lmax2 + 1): A, B = complex(0), complex(0) for ld in range(max(abs(l1 - l2), abs(m1 - m2)), l1 + l2 + 1): # if ld<abs(m1-m2) then P=0 a5, b5 = trf.ab5_coefficients(l2, m2, l1, m1, ld) A += a5 * bessel_h[ld] * legendre[ld][abs(m1 - m2)] B += b5 * bessel_h[ld] * legendre[ld][abs(m1 - m2)] A, B = eimph * A, eimph * B for tau1 in range(2): n1 = flds.multi_to_single_index( tau1, l1, m1, lmax1, mmax1) for tau2 in range(2): n2 = flds.multi_to_single_index( tau2, l2, m2, lmax2, mmax2) if tau1 == tau2: w[n1, n2] = A else: w[n1, n2] = B return w