def test_layerresponse_method(): fromlayer = 2 tolayer = 1 kp = np.linspace(0, 2) * omega a = np.linspace(0, 2 * np.pi) layer_system = lay.LayerSystem(thicknesses=layer_d, refractive_indices=layer_n) ref = [0, 0, layer_system.reference_z(fromlayer)] pwe_up = fldex.PlaneWaveExpansion(k=omega * 1.2, k_parallel=kp, azimuthal_angles=a, kind='upgoing', reference_point=ref) pwe_up.coefficients[0, :, :] = np.exp(-pwe_up.k_parallel_grid() / omega) pwe_down = fldex.PlaneWaveExpansion(k=omega * 1.2, k_parallel=kp, azimuthal_angles=a, kind='downgoing', reference_point=ref) pwe_down.coefficients[0, :, :] = 2j * np.exp( -pwe_up.k_parallel_grid() / omega * 3) pwe_r_up, pwe_r_down = layer_system.response(pwe_up, fromlayer, tolayer) pwe_r_up2, pwe_r_down2 = layer_system.response(pwe_down, fromlayer, tolayer) pwe_r_up3, pwe_r_down3 = layer_system.response((pwe_up, pwe_down), fromlayer, tolayer)
def plane_wave_expansion(self, layer_system, i): """Plane wave expansion for the plane wave including its layer system response. As it already is a plane wave, the plane wave expansion is somehow trivial (containing only one partial wave, i.e., a discrete plane wave expansion). Args: layer_system (smuthi.layers.LayerSystem): Layer system object i (int): layer number in which the plane wave expansion is valid Returns: Tuple of smuthi.field_expansion.PlaneWaveExpansion objects. The first element is an upgoing PWE, whereas the second element is a downgoing PWE. """ if np.cos(self.polar_angle) > 0: iP = 0 kind = 'upgoing' else: iP = layer_system.number_of_layers() - 1 kind = 'downgoing' niP = layer_system.refractive_indices[iP] neff = np.sin([self.polar_angle]) * niP alpha = np.array([self.azimuthal_angle]) angular_frequency = coord.angular_frequency(self.vacuum_wavelength) k_iP = niP * angular_frequency k_Px = k_iP * np.sin(self.polar_angle) * np.cos(self.azimuthal_angle) k_Py = k_iP * np.sin(self.polar_angle) * np.sin(self.azimuthal_angle) k_Pz = k_iP * np.cos(self.polar_angle) z_iP = layer_system.reference_z(iP) amplitude_iP = self.amplitude * np.exp( -1j * (k_Px * self.reference_point[0] + k_Py * self.reference_point[1] + k_Pz * (self.reference_point[2] - z_iP))) loz = layer_system.lower_zlimit(iP) upz = layer_system.upper_zlimit(iP) pwe_exc = fldex.PlaneWaveExpansion(k=k_iP, k_parallel=neff * angular_frequency, azimuthal_angles=alpha, kind=kind, reference_point=[0, 0, z_iP], lower_z=loz, upper_z=upz) pwe_exc.coefficients[self.polarization, 0, 0] = amplitude_iP pwe_up, pwe_down = layer_system.response(pwe_exc, from_layer=iP, to_layer=i) if iP == i: if kind == 'upgoing': pwe_up = pwe_up + pwe_exc elif kind == 'downgoing': pwe_down = pwe_down + pwe_exc return pwe_up, pwe_down
def plane_wave_expansion(self, layer_system, i, k_parallel_array=None, azimuthal_angles_array=None): """Plane wave expansion of the Gaussian beam. Args: layer_system (smuthi.layer.LayerSystem): stratified medium i (int): layer number in which to evaluate the expansion k_parallel_array (numpy.ndarray): in-plane wavenumber array for the expansion. if none specified, self.k_parallel_array is used azimuthal_angles_array (numpy.ndarray): azimuthal angles for the expansion. if none specified, self.azimuthal_angles_array is used Returns: tuple of to smuthi.field_expansion.PlaneWaveExpansion objects, one for upgoing and one for downgoing component """ if k_parallel_array is None: k_parallel_array = self.k_parallel_array if azimuthal_angles_array is None: azimuthal_angles_array = self.azimuthal_angles_array if np.cos(self.polar_angle) > 0: iG = 0 # excitation layer number kind = 'upgoing' else: iG = layer_system.number_of_layers() - 1 kind = 'downgoing' niG = layer_system.refractive_indices[ iG] # refractive index in excitation layer if niG.imag: warnings.warn('beam coming from absorbing medium') k_iG = niG * self.angular_frequency() z_iG = layer_system.reference_z(iG) loz = layer_system.lower_zlimit(iG) upz = layer_system.upper_zlimit(iG) pwe_exc = fldex.PlaneWaveExpansion( k=k_iG, k_parallel=k_parallel_array, azimuthal_angles=azimuthal_angles_array, kind=kind, reference_point=[0, 0, z_iG], lower_z=loz, upper_z=upz) k_Gx = k_iG * np.sin(self.polar_angle) * np.cos(self.azimuthal_angle) k_Gy = k_iG * np.sin(self.polar_angle) * np.sin(self.azimuthal_angle) kp = pwe_exc.k_parallel_grid() al = pwe_exc.azimuthal_angle_grid() kx = kp * np.cos(al) ky = kp * np.sin(al) kz = pwe_exc.k_z_grid() w = self.beam_waist r_G = self.reference_point g = (self.amplitude * w**2 / (4 * np.pi) * np.exp(-w**2 / 4 * ((kx - k_Gx)**2 + (ky - k_Gy)**2)) * np.exp(-1j * (kx * r_G[0] + ky * r_G[1] + kz * (r_G[2] - z_iG)))) pwe_exc.coefficients[0, :, :] = g * np.cos( al - self.azimuthal_angle + self.polarization * np.pi / 2) if np.cos(self.polar_angle) > 0: pwe_exc.coefficients[1, :, :] = g * np.sin( al - self.azimuthal_angle + self.polarization * np.pi / 2) else: pwe_exc.coefficients[1, :, :] = -g * np.sin( al - self.azimuthal_angle + self.polarization * np.pi / 2) pwe_up, pwe_down = layer_system.response(pwe_exc, from_layer=iG, to_layer=i) if iG == i: if kind == 'upgoing': pwe_up = pwe_up + pwe_exc elif kind == 'downgoing': pwe_down = pwe_down + pwe_exc return pwe_up, pwe_down
def response(self, pwe, from_layer, to_layer): """Evaluate the layer system response to an electromagnetic excitation inside the layer system. Args: pwe (tuple or smuthi.field_expansion.PlaneWaveExpansion): Either specify a PlaneWaveExpansion object that that represents the electromagnetic excitation, or a tuple of two PlaneWaveExpansion objects representing the upwards- and downwards propagating partial waves of the excitation. from_layer (int): Layer number in which the excitation is located to_layer (int): Layer number in which the layer response is to be evaluated Returns: Tuple (pwe_up, pwe_sown) of PlaneWaveExpansion objects representing the layer system response to the excitation. """ if hasattr(pwe, '__len__'): if len(pwe) == 2: pwe_up_0, pwe_down_0 = self.response(pwe[0], from_layer, to_layer) pwe_up_1, pwe_down_1 = self.response(pwe[1], from_layer, to_layer) pwe_up = pwe_up_0 + pwe_up_1 pwe_down = pwe_down_0 + pwe_down_1 else: raise ValueError( 'pwe argument must be either PlaneWaveExpansion or tuple of length two' ) else: assert pwe.reference_point == [0, 0, self.reference_z(from_layer)] omega = pwe.k / self.refractive_indices[from_layer] k_to_layer = omega * self.refractive_indices[to_layer] reference_point = [0, 0, self.reference_z(to_layer)] loz, upz = self.lower_zlimit(to_layer), self.upper_zlimit(to_layer) pwe_up = fldex.PlaneWaveExpansion( k=k_to_layer, k_parallel=pwe.k_parallel, azimuthal_angles=pwe.azimuthal_angles, kind='upgoing', reference_point=reference_point, lower_z=loz, upper_z=upz) pwe_down = fldex.PlaneWaveExpansion( k=k_to_layer, k_parallel=pwe.k_parallel, azimuthal_angles=pwe.azimuthal_angles, kind='downgoing', reference_point=reference_point, lower_z=loz, upper_z=upz) for pol in range(2): L = layersystem_response_matrix(pol, self.thicknesses, self.refractive_indices, pwe.k_parallel, omega, from_layer, to_layer) if pwe.kind == 'upgoing': pwe_up.coefficients[pol, :, :] = L[ 0, 0, :][:, None] * pwe.coefficients[pol, :, :] pwe_down.coefficients[pol, :, :] = L[ 1, 0, :][:, None] * pwe.coefficients[pol, :, :] elif pwe.kind == 'downgoing': pwe_up.coefficients[pol, :, :] = L[ 0, 1, :][:, None] * pwe.coefficients[pol, :, :] pwe_down.coefficients[pol, :, :] = L[ 1, 1, :][:, None] * pwe.coefficients[pol, :, :] else: raise ValueError('pwe type undefined') return pwe_up, pwe_down
vb = [0, 800] layer_system = lay.LayerSystem([0, 0], [1, 1]) x = np.array([fieldpoint[0]]) y = np.array([fieldpoint[1]]) z = np.array([fieldpoint[2]]) swe = fldex.SphericalWaveExpansion(k=k, l_max=3, m_max=3, kind='outgoing', reference_point=swe_ref) swe.coefficients[0] = 2 swe.coefficients[1] = -3j swe.coefficients[16] = 1 swe.coefficients[18] = 0.5 kp2 = np.linspace(0, 2, num=1000) * k pwe = fldex.PlaneWaveExpansion(k=k, k_parallel=kp2, azimuthal_angles=a, kind='upgoing', reference_point=pwe_ref, lower_z=vb[0], upper_z=vb[1]) pwe.coefficients[0, :, :] = 100000 * np.exp(- pwe.k_parallel_grid() / k / 20) pwe.coefficients[1, :, :] = -100000 * np.exp(- pwe.k_parallel_grid() / k / 10) def test_swe2pwe(): ex, ey, ez = swe.electric_field(x, y, z) pwe_up, pwe_down = fldex.swe_to_pwe_conversion(swe, k_parallel=kp, azimuthal_angles=a, layer_system=layer_system) ex2, ey2, ez2 = pwe_up.electric_field(x, y, z) err2 = abs(ex - ex2) ** 2 + abs(ey - ey2) ** 2 + abs(ez - ez2) ** 2 norme2 = abs(ex) ** 2 + abs(ey) ** 2 + abs(ez) ** 2 print('relative error:', np.sqrt(err2 / norme2)) assert np.sqrt(err2 / norme2) < 5e-3 def test_pwe2swe():
def scattered_field_pwe(vacuum_wavelength, particle_list, layer_system, layer_number, k_parallel='default', azimuthal_angles='default', include_direct=True, include_layer_response=True): """Calculate the plane wave expansion of the scattered field of a set of particles. Args: vacuum_wavelength (float): Vacuum wavelength (length unit) particle_list (list): List of Particle objects layer_system (smuthi.layers.LayerSystem): Stratified medium layer_number (int): Layer number in which the plane wave expansion should be valid k_parallel (numpy.ndarray or str): in-plane wavenumbers array. if 'default', use smuthi.coordinates.default_k_parallel azimuthal_angles (numpy.ndarray or str): azimuthal angles array if 'default', use smuthi.coordinates.default_azimuthal_angles include_direct (bool): If True, include the direct scattered field include_layer_response (bool): If True, include the layer system response Returns: A tuple of PlaneWaveExpansion objects for upgoing and downgoing waves. """ sys.stdout.write( 'Evaluating scattered field plane wave expansion in layer number %i ...\n' % layer_number) sys.stdout.flush() omega = coord.angular_frequency(vacuum_wavelength) k = omega * layer_system.refractive_indices[layer_number] z = layer_system.reference_z(layer_number) vb = (layer_system.lower_zlimit(layer_number), layer_system.upper_zlimit(layer_number)) pwe_up = fldex.PlaneWaveExpansion(k=k, k_parallel=k_parallel, azimuthal_angles=azimuthal_angles, kind='upgoing', reference_point=[0, 0, z], lower_z=vb[0], upper_z=vb[1]) pwe_down = fldex.PlaneWaveExpansion(k=k, k_parallel=k_parallel, azimuthal_angles=azimuthal_angles, kind='downgoing', reference_point=[0, 0, z], lower_z=vb[0], upper_z=vb[1]) for iS, particle in enumerate( tqdm(particle_list, desc='Scatt. field pwe ', file=sys.stdout, bar_format='{l_bar}{bar}| elapsed: {elapsed} ' 'remaining: {remaining}')): i_iS = layer_system.layer_number(particle.position[2]) # direct contribution if i_iS == layer_number and include_direct: pu, pd = fldex.swe_to_pwe_conversion( swe=particle.scattered_field, k_parallel=k_parallel, azimuthal_angles=azimuthal_angles, layer_system=layer_system) pwe_up = pwe_up + pu pwe_down = pwe_down + pd # layer mediated contribution if include_layer_response: pu, pd = fldex.swe_to_pwe_conversion( swe=particle.scattered_field, k_parallel=k_parallel, azimuthal_angles=azimuthal_angles, layer_system=layer_system, layer_number=layer_number, layer_system_mediated=True) pwe_up = pwe_up + pu pwe_down = pwe_down + pd return pwe_up, pwe_down
def scattered_field_piecewise_expansion(vacuum_wavelength, particle_list, layer_system, k_parallel='default', azimuthal_angles='default', layer_numbers=None): """Compute a piecewise field expansion of the scattered field. Args: vacuum_wavelength (float): vacuum wavelength particle_list (list): list of smuthi.particles.Particle objects layer_system (smuthi.layers.LayerSystem): stratified medium k_parallel (numpy.ndarray or str): in-plane wavenumbers array. if 'default', use smuthi.coordinates.default_k_parallel azimuthal_angles (numpy.ndarray or str): azimuthal angles array if 'default', use smuthi.coordinates.default_azimuthal_angles layer_numbers (list): if specified, append only plane wave expansions for these layers Returns: scattered field as smuthi.field_expansion.PiecewiseFieldExpansion object """ if layer_numbers is None: layer_numbers = range(layer_system.number_of_layers()) sfld = fldex.PiecewiseFieldExpansion() for i in tqdm(layer_numbers, desc='Scatt. field expansion ', file=sys.stdout, bar_format='{l_bar}{bar}| elapsed: {elapsed} ' 'remaining: {remaining}'): # layer mediated scattered field --------------------------------------------------------------------------- k = coord.angular_frequency( vacuum_wavelength) * layer_system.refractive_indices[i] ref = [0, 0, layer_system.reference_z(i)] vb = (layer_system.lower_zlimit(i), layer_system.upper_zlimit(i)) pwe_up = fldex.PlaneWaveExpansion(k=k, k_parallel=k_parallel, azimuthal_angles=azimuthal_angles, kind='upgoing', reference_point=ref, lower_z=vb[0], upper_z=vb[1]) pwe_down = fldex.PlaneWaveExpansion(k=k, k_parallel=k_parallel, azimuthal_angles=azimuthal_angles, kind='downgoing', reference_point=ref, lower_z=vb[0], upper_z=vb[1]) for particle in particle_list: add_up, add_down = fldex.swe_to_pwe_conversion( particle.scattered_field, k_parallel, azimuthal_angles, layer_system, i, True) pwe_up = pwe_up + add_up pwe_down = pwe_down + add_down # in bottom_layer, suppress upgoing waves, and in top layer, suppress downgoing waves if i > 0: sfld.expansion_list.append(pwe_up) if i < layer_system.number_of_layers() - 1: sfld.expansion_list.append(pwe_down) # direct field --------------------------------------------------------------------------------------------- for particle in particle_list: sfld.expansion_list.append(particle.scattered_field) return sfld