def test_interface_z_translation(s1, s2, rtol): """ Moving the source and particle is identical to moving the interface (cross-section comparison) """ interface = miepy.interface(miepy.constant_material(index=1.7)) cluster = miepy.sphere_cluster(position=[0, 0, -zpos], radius=radius, material=material, medium=medium, lmax=2, source=s1, interface=interface, wavelength=wavelength) C1 = np.array(cluster.cross_sections()) interface = miepy.interface(miepy.constant_material(index=1.7), z=zpos) cluster = miepy.sphere_cluster(position=[0, 0, 0], radius=radius, material=material, medium=medium, lmax=2, source=s2, interface=interface, wavelength=wavelength) C2 = np.array(cluster.cross_sections()) assert np.allclose(C1, C2, atol=0, rtol=rtol)
def test_medium_scaling_force(plot=False): """ensure that the radiation pressure on a single sphere scales with background index as n^5 for small, high-index sphere""" n_b = np.linspace(1, 5, 10) F = np.zeros_like(n_b) for i, n in enumerate(n_b): sphere = miepy.sphere_cluster( position=[0, 0, 0], radius=2 * nm, material=miepy.constant_material(40**2), medium=miepy.constant_material(n**2), source=miepy.sources.plane_wave.from_string(polarization='x'), wavelength=2800 * nm, lmax=2) F[i] = sphere.force_on_particle(0)[2] F_fit = np.max(F) * (n_b / np.max(n_b))**5 if not plot: L2 = np.linalg.norm(F - F_fit) / F.shape[0] avg = np.average(F + F_fit) / 2 print(L2, avg) assert L2 < 1e-2 * avg else: plt.figure() plt.plot(n_b, F, label='exact force') plt.plot(n_b, F_fit, 'o', label='$n_b^5$ scaling') plt.xlabel('$n_b$') plt.ylabel('force') plt.legend() plt.title(test_medium_scaling_force.__name__, weight='bold')
def vis(): ### forces fig, ax = plt.subplots() force = np.zeros([3, len(separations)]) for i, separation in enumerate(separations): var = job.load(sim, f'p{i}') force[0, i] = var.Fx force[1, i] = var.Fy force[2, i] = var.Fz norm = job.load(norm_sim) ax.axhline(0, linestyle='--', color='black') ax.plot(separations / nm, force[0] / norm.norm * norm.area * constants.epsilon_0 / 2, 'o', color='C0', label='Fx (FDTD)') ax.plot(separations / nm, force[1] / norm.norm * norm.area * constants.epsilon_0 / 2, 'o', color='C1', label='Fy (FDTD)') ax.plot(separations / nm, force[2] / norm.norm * norm.area * constants.epsilon_0 / 2, 'o', color='C2', label='Fz (FDTD)') import miepy eps = meep_ext.get_eps(gold)(wavelength) Au = miepy.constant_material(eps * scale**2) water = miepy.constant_material(nb**2) source = miepy.sources.rhc_polarized_plane_wave() seps = np.linspace(300 * nm / scale, 900 * nm / scale, 100) force = np.zeros([3, len(seps)]) for i, sep in enumerate(seps): spheres = miepy.spheres([[-sep / 2, 0, 0], [sep / 2, 0, 0]], radius / scale, Au) sol = miepy.gmt(spheres, source, wavelength, 2, medium=water) F = sol.force_on_particle(1) force[:, i] = F.squeeze() ax.plot(seps * scale / nm, force[0], color='C0', label='Fx (GMT)') ax.plot(seps * scale / nm, force[1], color='C1', label='Fy (GMT)') ax.plot(seps * scale / nm, force[2], color='C2', label='Fz (GMT)') ax.set(xlabel='separation (nm)', ylabel='force') ax.legend() plt.show()
def test_cross_section_methods_monomer(plot=False): """test two cross-section methods for a single particle (monomer)""" C1 = np.zeros_like(wavelengths) C2 = np.zeros([len(wavelengths), 2, 2]) for i, wavelength in enumerate(tqdm(wavelengths)): cluster = miepy.sphere_cluster( position=[0, 0, 0], radius=75 * nm, material=miepy.constant_material(3.7**2), source=miepy.sources.plane_wave.from_string(polarization='x'), lmax=2, wavelength=wavelength) C1[i] = cluster.cross_sections().scattering C2[i] = cluster.cross_sections_per_multipole().scattering C2_sum = np.sum(C2, axis=(1, 2)) if not plot: L2 = np.linalg.norm(C1 - C2_sum) / C1.shape[0] avg = np.average(C1 + C2_sum) / 2 print(L2, avg) assert L2 < 1e-4 * avg else: plt.figure() plt.plot(wavelengths / nm, C1, label='total scattering') plt.plot(wavelengths / nm, C2_sum, 'o', label='total scattering') plt.plot(wavelengths / nm, C2[:, 0, 0], label='eD') plt.plot(wavelengths / nm, C2[:, 1, 0], label='mD') plt.plot(wavelengths / nm, C2[:, 0, 1], label='eQ') plt.plot(wavelengths / nm, C2[:, 1, 1], label='mQ') plt.legend()
def test_cluster_field_near_to_far(): """ Compare scattered E/H-field of a cluster in far field using near and far field expressions Expressions are expected to converge in the limit r -> infinity """ x = np.linspace(-600 * nm, 600 * nm, 3) y = np.linspace(-600 * nm, 600 * nm, 3) cluster = miepy.sphere_cluster(position=[[xv, yv, 0] for xv in x for yv in y], radius=100 * nm, material=miepy.constant_material(index=2), wavelength=wav, source=miepy.sources.plane_wave([1, 1]), lmax=3) theta = np.linspace(0, np.pi, 5) phi = np.linspace(0, 2 * np.pi, 5) THETA, PHI = np.meshgrid(theta, phi) radius = np.ones_like(THETA) E1 = cluster.E_field(radius, THETA, PHI, spherical=True, source=False) E2 = cluster.E_angular(THETA, PHI, radius=radius, source=False) H1 = cluster.H_field(radius, THETA, PHI, spherical=True, source=False) H2 = cluster.H_angular(THETA, PHI, radius=radius, source=False) assert np.allclose(E1[0], 0, atol=1e-10), 'radial component of E goes to 0' assert np.allclose(E1[1:], E2, atol=0, rtol=1e-6), 'E converges' assert np.allclose(H1[0], 0, atol=1e-10), 'radial component of H goes to 0' assert np.allclose(H1[1:], H2, atol=0, rtol=1e-6), 'H converges'
def test_maxwells_equations_cluster_near_field(source): """ Verify Maxwell's equations for a cluster at a point in the near field """ cluster = miepy.sphere_cluster(position=[[-400 * nm, -200 * nm, 0], [200 * nm, 200 * nm, 100 * nm]], radius=100 * nm, material=miepy.constant_material(index=3.7), source=source, wavelength=wav, lmax=2) x0, y0, z0 = 0, 0, 0 eps = .1 * nm x = np.linspace(x0, x0 + eps, 2) y = np.linspace(y0, y0 + eps, 2) z = np.linspace(z0, z0 + eps, 2) X, Y, Z = np.meshgrid(x, y, z, indexing='ij') E_grid = cluster.E_field(X, Y, Z) E = np.average(E_grid, axis=(1, 2, 3)) divE = div(E_grid, eps) curlE = curl(E_grid, eps) H_grid = cluster.H_field(X, Y, Z, k) H = np.average(H_grid, axis=(1, 2, 3)) divH = div(H_grid, eps) curlH = curl(H_grid, eps) assert np.abs(divE / (k * np.linalg.norm(E))) < 1e-6, 'div(E) = 0' assert np.abs(divH / (k * np.linalg.norm(H))) < 1e-6, 'div(H) = 0' assert np.allclose(curlE, 1j * k * H, atol=0, rtol=1e-6), 'curl(E) = ikH' assert np.allclose(curlH, -1j * k * E, atol=0, rtol=1e-6), 'curl(H) = -ikE'
def force_gmmt(): wavelengths = np.linspace(400 * nm, 1000 * nm, 100) # eps = meep_ext.get_eps(material)(wavelengths) # Au = miepy.data_material(wavelengths, eps) material = miepy.constant_material(3.5**2) particles = [ miepy.cube([-sep / 2, 0, 0], 200 * nm, material), miepy.cube([sep / 2, 0, 0], 200 * nm, material) ] F1 = np.zeros((3, ) + wavelengths.shape, dtype=float) F2 = np.zeros((3, ) + wavelengths.shape, dtype=float) for i, wavelength in enumerate(pbar(wavelengths)): c = miepy.cluster(particles=particles, source=miepy.sources.plane_wave([1, 0]), wavelength=wavelength, lmax=4) q = miepy.quaternion.from_spherical_coords(0, 0) c.update(orientation=[q, q]) F1[:, i] = c.force_on_particle(1) q = miepy.quaternion.from_spherical_coords(0, np.pi / 4) c.update(orientation=[q, q]) F2[:, i] = c.force_on_particle(1) return dict(wavelengths=wavelengths, F1=F1, F2=F2)
def test_boundary_conditions(): """verifies the continunity of tangential components of E and H at the surface of a particle""" radius = 50 * nm cluster = miepy.sphere_cluster( position=[[0, 0, 0]], radius=radius, material=miepy.materials.Ag(), lmax=2, wavelength=600 * nm, source=miepy.sources.plane_wave.from_string(polarization='y'), medium=miepy.constant_material(1.2**2)) theta = 0.3 phi = 0.3 eps = .1 * nm E_out = cluster.E_field(radius + eps, theta, phi, spherical=True) E_in = cluster.E_field(radius - eps, theta, phi, spherical=True) H_out = cluster.H_field(radius + eps, theta, phi, spherical=True) H_in = cluster.H_field(radius - eps, theta, phi, spherical=True) assert np.allclose(E_out[1:], E_in[1:], atol=4e-2, rtol=0) assert np.allclose(H_out[1:], H_in[1:], atol=4e-2, rtol=0)
def __init__(self, radius_in, radius_out, material_in, material_out, wavelength, lmax, medium=None): """Solve traditional Mie theory: a single cores-shell in x-polarized plane wave illumination radius_in core radius radius_out core+shell radius material_in core material material_out shell material wavelength[N] wavelength(s) to solve the system at lmax maximum number of orders to use in angular momentum expansion medium material medium (must be non-absorbing; defaults to vacuum) """ self.radius_in = radius_in self.radius_out = radius_out self.material_in = material_in self.material_out = material_out self.wavelength = np.asarray(np.atleast_1d(wavelength), dtype=float) self.lmax = lmax if medium is None: self.medium = miepy.constant_material(1.0, 1.0) else: self.medium = medium if (self.medium.eps(self.wavelength).imag != 0).any() \ or (self.medium.mu(self.wavelength).imag != 0).any(): raise ValueError('medium must be non-absorbing') self.Nfreq = len(self.wavelength) self.material_data = {} self.material_data['wavelength'] = self.wavelength self.material_data['eps_in'] = self.material_in.eps(self.wavelength) self.material_data['mu_in'] = self.material_in.mu(self.wavelength) self.material_data['n_in'] = np.sqrt(self.material_data['eps_in']*self.material_data['mu_in']) self.material_data['eps_out'] = self.material_out.eps(self.wavelength) self.material_data['mu_out'] = self.material_out.mu(self.wavelength) self.material_data['n_out'] = np.sqrt(self.material_data['eps_out']*self.material_data['mu_out']) self.material_data['eps_b'] = self.medium.eps(self.wavelength) self.material_data['mu_b'] = self.medium.mu(self.wavelength) self.material_data['n_b'] = np.sqrt(self.material_data['eps_b']*self.material_data['mu_b']) self.material_data['k'] = 2*np.pi*self.material_data['n_b']/self.wavelength self.an = np.zeros((self.Nfreq, self.lmax), dtype=np.complex) self.bn = np.zeros((self.Nfreq, self.lmax), dtype=np.complex) self.cn = np.zeros((self.Nfreq, self.lmax), dtype=np.complex) self.dn = np.zeros((self.Nfreq, self.lmax), dtype=np.complex) self.scattering_properties = (self.an, self.bn, self.material_data['k']) self.computed = False
def __init__(self, radius, material, wavelength, lmax, medium=None): """Solve traditional Mie theory: a single sphere in x-polarized plane wave illumination radius particle radius material particle material wavelength[N] wavelength(s) to solve the system at lmax maximum number of orders to use in angular momentum expansion medium material medium (must be non-absorbing; defaults to vacuum) """ self.radius = radius self.material = material self.wavelength = np.asarray(np.atleast_1d(wavelength), dtype=float) self.lmax = lmax if medium is None: self.medium = miepy.constant_material(1.0, 1.0) else: self.medium = medium if (self.medium.eps(self.wavelength).imag != 0).any() \ or (self.medium.mu(self.wavelength).imag != 0).any(): raise ValueError('medium must be non-absorbing') self.Nfreq = len(self.wavelength) self.material_data = {} self.material_data['wavelength'] = self.wavelength self.material_data['eps'] = self.material.eps(self.wavelength) self.material_data['mu'] = self.material.mu(self.wavelength) self.material_data['n'] = np.sqrt(self.material_data['eps'] * self.material_data['mu']) self.material_data['eps_b'] = self.medium.eps(self.wavelength) self.material_data['mu_b'] = self.medium.mu(self.wavelength) self.material_data['n_b'] = np.sqrt(self.material_data['eps_b'] * self.material_data['mu_b']) self.material_data[ 'k'] = 2 * np.pi * self.material_data['n_b'] / self.wavelength #TODO (performance) swap an/bn shape for lmax by Nfreq, remove transpose self.an = np.zeros((self.Nfreq, self.lmax), dtype=np.complex) self.bn = np.zeros((self.Nfreq, self.lmax), dtype=np.complex) self.cn = np.zeros((self.Nfreq, self.lmax), dtype=np.complex) self.dn = np.zeros((self.Nfreq, self.lmax), dtype=np.complex) self.scattering_properties = (self.an, self.bn, self.material_data['k']) self.exterior_computed = False self.interior_computed = False
def vis(): ### cross-sections fig, ax = plt.subplots() scat = np.zeros([len(separations)]) absorb = np.zeros([len(separations)]) for i, separation in enumerate(separations): norm = job.load(norm_sim, f'p{i}') var = job.load(sim, f'p{i}') scat[i] = var.scattering / norm.norm * norm.area absorb[i] = var.absorption / norm.norm * norm.area ax.plot(separations / nm, scat, 'o', color='C0', label='scattering (FDTD)') ax.plot(separations / nm, absorb, 'o', color='C1', label='absorption (FDTD)') ax.plot(separations / nm, scat + absorb, 'o', color='C2', label='extinction (FDTD)') import miepy eps = meep_ext.get_eps(gold)(wavelength) Au = miepy.constant_material(eps) source = miepy.sources.rhc_polarized_plane_wave() seps = np.linspace(300 * nm, 900 * nm, 100) scat = np.zeros([len(seps)]) absorb = np.zeros([len(seps)]) extinct = np.zeros([len(seps)]) for i, sep in enumerate(seps): spheres = miepy.spheres([[-sep / 2, 0, 0], [sep / 2, 0, 0]], radius, Au) sol = miepy.gmt(spheres, source, wavelength, 2) scat[i], absorb[i], extinct[i] = sol.cross_sections() ax.plot(seps / nm, scat, color='C0', label='scattering (GMT)') ax.plot(seps / nm, absorb, color='C1', label='absorption (GMT)') ax.plot(seps / nm, extinct, color='C2', label='extinction (GMT)') ax.set(xlabel='separation (nm)', ylabel='cross-section') ax.legend() plt.show()
def vis(): ### forces fig, axes = plt.subplots(nrows=2, figsize=(7,6), sharex=True, gridspec_kw=dict(height_ratios=[2,1], hspace=0.05)) force = np.zeros([3,len(separations)]) for i,separation in enumerate(separations): var = job.load(sim, f'p{i}') force[0,i] = var.Fx force[1,i] = var.Fy force[2,i] = var.Fz norm = job.load(norm_sim) for ax in axes: ax.axhline(0, linestyle='--', color='black') ax.plot(separations/nm, force[0]/norm.norm*norm.area*constants.epsilon_0/2*1e25, 'o', color='C0', label='Fx (FDTD)') ax.plot(separations/nm, force[1]/norm.norm*norm.area*constants.epsilon_0/2*1e25, 'o', color='C1', label='Fy (FDTD)') ax.plot(separations/nm, force[2]/norm.norm*norm.area*constants.epsilon_0/2*1e25, 'o', color='C2', label='Fz (FDTD)') import miepy eps = meep_ext.get_eps(gold)(wavelength) Au = miepy.constant_material(eps) # Au = miepy.constant_material(3.5**2) source = miepy.sources.rhc_polarized_plane_wave() seps = np.linspace(300*nm, 900*nm, 100) force = np.zeros([3,len(seps)]) for i,sep in enumerate(seps): spheres = miepy.spheres([[-sep/2,0,0],[sep/2,0,0]], radius, Au) sol = miepy.gmt(spheres, source, wavelength, 2) F = sol.force_on_particle(1) force[:,i] = F.squeeze() for ax in axes: ax.plot(seps/nm, force[0]*1e25, color='C0', label='Fx (GMT)') ax.plot(seps/nm, force[1]*1e25, color='C1', label='Fy (GMT)') ax.plot(seps/nm, force[2]*1e25, color='C2', label='Fz (GMT)') axes[0].legend() axes[0].set(ylabel='force') axes[1].set(xlabel='separation (nm)', ylabel='force', ylim=[-3e-2, 3e-2]) plt.show()
def test_maxwells_equations_cluster_far_field(source): """ Verify Maxwell's equations for a cluster at a point in the far field """ cluster = miepy.sphere_cluster(position=[[-400 * nm, -200 * nm, 0], [200 * nm, 200 * nm, 100 * nm]], radius=100 * nm, material=miepy.constant_material(index=3.7), source=source, wavelength=wav, lmax=2) x0, y0, z0 = 0, 0, 1 eps = 1 * nm x = np.linspace(x0, x0 + eps, 2) y = np.linspace(y0, y0 + eps, 2) z = np.linspace(z0, z0 + eps, 2) X, Y, Z = np.meshgrid(x, y, z, indexing='ij') R, THETA, PHI = miepy.coordinates.cart_to_sph(X, Y, Z) E_grid = cluster.E_angular(THETA, PHI, radius=R) E_grid = np.insert(E_grid, 0, 0, axis=0) E_grid = miepy.coordinates.vec_sph_to_cart(E_grid, THETA, PHI) E = np.average(E_grid, axis=(1, 2, 3)) divE = div(E_grid, eps) curlE = curl(E_grid, eps) H_grid = cluster.H_angular(THETA, PHI, radius=R) H_grid = np.insert(H_grid, 0, 0, axis=0) H_grid = miepy.coordinates.vec_sph_to_cart(H_grid, THETA, PHI) H = np.average(H_grid, axis=(1, 2, 3)) divH = div(H_grid, eps) curlH = curl(H_grid, eps) assert np.abs(divE / (k * np.linalg.norm(E))) < 1e-6, 'div(E) = 0' assert np.abs(divH / (k * np.linalg.norm(H))) < 1e-6, 'div(H) = 0' assert np.allclose(curlE[:2], 1j * k * H[:2], atol=0, rtol=1e-5), 'curl(E) = ikH' assert np.allclose(curlH[:2], -1j * k * E[:2], atol=0, rtol=1e-5), 'curl(H) = -ikE'
def gmt_sim(): wavelengths = np.linspace(400 * nm, 1000 * nm, 100) eps = meep_ext.get_eps(material)(wavelengths) Au = miepy.data_material(wavelengths, eps) Au = miepy.constant_material(3.5**2) C, A, E = [np.zeros_like(wavelengths) for i in range(3)] particles = [] # orientation = miepy.quaternion.from_spherical_coords(theta[i], phi[i]) particles.append(miepy.cube([0, 0, 0], W, material=Au, orientation=q)) for i, wavelength in enumerate(tqdm(wavelengths)): sol = miepy.cluster(particles=particles, source=miepy.sources.plane_wave([1, 0]), wavelength=wavelength, lmax=4) C[i], A[i], E[i] = sol.cross_sections() return dict(wavelengths=wavelengths, C=C, A=A, E=E)
nm = 1e-9 r = 20 * nm fig, axes = plt.subplots(ncols=2, figsize=plt.figaspect(1 / 2)) Nx = 50 Ny = 50 x = np.linspace(-5 * r, 5 * r, Nx) y = np.linspace(-5 * r, 5 * r, Ny) z = np.array([0]) X, Y, Z = np.meshgrid(x, y, z, indexing='xy') R = (X**2 + Y**2 + Z**2)**0.5 THETA = np.arccos(Z / R) PHI = np.arctan2(Y, X) system = miepy.gmt(miepy.spheres([0, 0, 0], r, miepy.constant_material(1.3)), miepy.sources.plane_wave.from_string(polarization='x'), 600 * nm, 2, interactions=False) E = np.squeeze(system.E_field(X, Y, Z, False)) I = np.sum(np.abs(E)**2, axis=0) print(E[:, 0, 0]) mask = np.zeros((Nx, Ny), dtype=bool) mask[(np.squeeze(Y))**2 + np.squeeze(X)**2 < 3.5 * r**2] = True I[mask] = 0 im = axes[0].pcolormesh(np.squeeze(X) / nm, np.squeeze(Y) / nm,
def transmission_coefficients(self, theta, wavelength, medium): m = self.get_relative_index(wavelength, medium) theta_t = self.transmission_angle(theta, wavelength, medium) t_parallel = 2 * np.cos(theta) / (m * np.cos(theta) + np.cos(theta_t)) t_perp = 2 * np.cos(theta) / (np.cos(theta) + m * np.cos(theta_t)) return t_parallel, t_perp if __name__ == '__main__': n1 = 1 n2 = 500 + 2j wavelength = 1 k1 = n1 * 2 * np.pi / wavelength k2 = n2 * 2 * np.pi / wavelength medium = miepy.constant_material(index=n1) interface = miepy.interface(material=miepy.constant_material(index=n2)) incident = miepy.sources.plane_wave([1, 0], 0) reflected = interface.reflected_plane_wave(incident, wavelength, medium) transmitted = interface.transmitted_plane_wave(incident, wavelength, medium) x = np.linspace(-3, 3, 300) z = np.linspace(-3, 3, 300) X, Z = np.meshgrid(x, z) Y = np.zeros_like(X) E = np.zeros((3, ) + X.shape, dtype=complex) idx = Z < 0
import numpy as np import miepy import pytest nm = 1e-9 Ag = miepy.materials.Ag() metal = miepy.materials.metal() eps_b = 1.5 medium = miepy.constant_material(index=eps_b**2) radius = 75*nm source = miepy.sources.plane_wave([1,0]) wavelength = 600*nm lmax = 2 @pytest.mark.parametrize("material,atol", [ (Ag, 2e-16), (metal, 2e-17), ]) def test_tmatrix_sphere_is_sphere(material, atol): """tmatrix method with spheres should be equivalent to sphere cluster""" position = [[-300*nm, 0, 0], [300*nm, 0, 0]] spheres = miepy.sphere_cluster(position=position, radius=radius, material=material, source=source, wavelength=wavelength,
""" Comparison of single Mie theory and GMT """ import numpy as np import miepy from tqdm import tqdm nm = 1e-9 # wavelength from 400nm to 1000nm wavelengths = np.linspace(400 * nm, 1000 * nm, 10) # create a material with n = 3.7 (eps = n^2) at all wavelengths dielectric = miepy.constant_material(3.7**2 + .1j) # calculate scattering coefficients radius = 100 * nm # 100 nm radius # water medium medium = miepy.materials.water() # Single Mie Theory lmax = 5 # Use up to 5 multipoles sphere = miepy.single_mie_sphere(radius, dielectric, wavelengths, lmax, medium=medium) S, A, E = sphere.cross_sections() Fz = sphere.radiation_force()
from tqdm import tqdm # Variable wavelengths and core index of refraction N_index = 250 N_wav = 250 data = np.zeros(shape=(N_index, N_wav)) indices = np.linspace(1, 4, N_index) wavelengths = np.linspace(400e-9, 1000e-9, N_wav) # Calculate scattering coefficients radius = 165e-9 # 165 nm radius lmax = 10 # Use up to 10 multipoles for i, index in enumerate(tqdm(indices)): dielectric = miepy.constant_material(index**2) sphere = miepy.single_mie_sphere(radius, dielectric, wavelengths, lmax) sphere.solve_exterior() S = sphere.cross_sections().scattering data[i] = S # Plot results plt.figure(1) data /= np.max(data) X, Y = np.meshgrid(indices, wavelengths * 1e9, indexing='ij') plt.pcolormesh(X, Y, data, shading="gouraud") plt.xlabel("index of refraction")
def __init__(self, *, position, radius, material, source, wavelength, lmax, medium=None, origin=None, symmetry=None, interface=None, interactions=True): """Arguments: position[N,3] or [3] sphere positions radius[N] or scalar sphere radii material[N] or scalar sphere materials source source object specifying the incident E and H functions wavelength wavelength to solve the system at lmax maximum number of orders to use in angular momentum expansion (int) medium (optional) material medium (must be non-absorbing; default=vacuum) origin (optional) system origin around which to compute cluster quantities (default = [0,0,0]). Choose 'auto' to automatically choose origin as center of geometry. symmetry (optional) specify system symmetries (default: no symmetries) interface (optional) include an infinite interface (default: no interface) interactions (optional) If True, include particle interactions (bool, default=True) """ ### sphere properties self.position = np.asarray(np.atleast_2d(position), dtype=float) self.radius = atleast(radius, dim=1, length=self.position.shape[0], dtype=float) self.material = atleast(material, dim=1, length=self.position.shape[0], dtype=np.object) if (self.position.shape[0] != self.radius.shape[0] != self.material.shape[0]): raise ValueError( "The shapes of position, radius, and material do not match") self.Nparticles = self.radius.shape[0] self.symmetry = symmetry self.interface = interface ### system properties self.source = source self.wavelength = wavelength self.lmax = lmax self.rmax = lmax * (lmax + 2) self.interactions = interactions ### set the origin self.auto_origin = False if origin is None: self.origin = np.zeros(3) elif origin == 'auto': self.auto_origin = True self.origin = np.average(self.position, axis=0) else: self.origin = np.asarray(origin) ### set the medium if medium is None: self.medium = miepy.constant_material(eps=1.0, mu=1.0) else: self.medium = medium if (self.medium.eps(self.wavelength).imag != 0) \ or (self.medium.mu(self.wavelength).imag != 0): raise ValueError('medium must be non-absorbing') ### build material data of particles self.material_data = miepy.material_functions.material_struct( self.material, self.medium, wavelength=self.wavelength) ### mie coefficients self.mie_scat = np.zeros([self.Nparticles, 2, self.lmax], dtype=complex) self.mie_int = np.zeros([self.Nparticles, 2, self.lmax], dtype=complex) for i in range(self.Nparticles): conducting = (self.material[i].name == 'metal') for n in range(1, self.lmax + 1): self.mie_scat[i,:,n-1] = \ miepy.mie_single.mie_sphere_scattering_coefficients(self.radius[i], n, self.material_data.eps[i], self.material_data.mu[i], self.material_data.eps_b, self.material_data.mu_b, self.material_data.k_b, conducting=conducting) self.mie_int[i,:,n-1] = \ miepy.mie_single.mie_sphere_interior_coefficients(self.radius[i], n, self.material_data.eps[i], self.material_data.mu[i], self.material_data.eps_b, self.material_data.mu_b, self.material_data.k_b, conducting=conducting) ### modified coefficients self.p_inc = np.zeros([self.Nparticles, 2, self.rmax], dtype=complex) self.p_scat = np.zeros([self.Nparticles, 2, self.rmax], dtype=complex) self.p_int = np.zeros([self.Nparticles, 2, self.rmax], dtype=complex) self.p_src = np.zeros([self.Nparticles, 2, self.rmax], dtype=complex) ### cluster coefficients self.p_cluster = None ### solve the interactions self.solve()
Q11 = get_Q(1, 1) T = -np.einsum('aibj,bjck->aick', Q11, np.linalg.tensorinv(Q31)) return T nm = 1e-9 lmax = 4 wavelength = 600 * nm radius = 60 * nm eps = 4 material = miepy.constant_material(eps) sphere = miepy.sphere([0, 0, 0], radius, material) T = sphere.compute_tmatrix(lmax, wavelength, 1) # print(T[0,:,0,0]) theta = np.linspace(0, np.pi, 80) phi = np.linspace(0, 2 * np.pi, 80) THETA, PHI = np.meshgrid(theta, phi, indexing='ij') rhat, that, phat = miepy.coordinates.sph_basis_vectors(THETA, PHI) dS = rhat * np.sin(THETA) * radius**2 # T = get_tmatrix(radius, dS, eps, 1, wavelength, lmax) # print(T[0,:,0,0]) def ellipsoid_dS(a, b, c, theta, phi):
sphere_z_global_offset_nm = 1000 x_bounds_nm = [ -1000, 1000 ] y_bounds_nm = [ -1000, 1000 ] device_size_x_nm = x_bounds_nm[ 1 ] - x_bounds_nm[ 0 ] device_size_y_nm = y_bounds_nm[ 1 ] - y_bounds_nm[ 0 ] sphere_radius_nm = 50 sphere_spacing_nm = 200 sphere_gen_probability = 0.1 sphere_index = 2.4 sphere_dielectric = miepy.constant_material( sphere_index**2 ) background_dielectric = miepy.constant_material( 1.46**2 ) interface_dielectric = miepy.materials.vacuum() # two_layers = smuthi.layers.LayerSystem( thicknesses=[0, 0], refractive_indices=[ 1.0, 1.46 ] ) # smuthi_plane_wave = smuthi.initial_field.PlaneWave( # vacuum_wavelength=probe_wavelength_nm, # polar_angle=np.pi,#np.pi,#4*np.pi/5, # from top # azimuthal_angle=0, # polarization=1 ) # 0=TE 1=TM plane_wave = miepy.sources.plane_wave( [ 1, 0 ] ) air_interface = miepy.interface( interface_dielectric, z=( ( device_height_nm + sphere_z_global_offset_nm ) * nm ) ) lmax = 2#3
""" Example of how to make a core-shell and plot scattering, absorption, and scattering per multipole """ import numpy as np import matplotlib.pyplot as plt import miepy # wavelength from 400nm to 1000nm wavelengths = np.linspace(400e-9,1000e-9,1000) # Ag shell and dielectric core Ag = miepy.materials.Ag() dielectric = miepy.constant_material(1.46**2) # Calculate scattering coefficients radius_in = 135e-9 radius_out = 145e-9 lmax = 10 # Use up to 10 multipoles core_shell = miepy.single_mie_core_shell(radius_in, radius_out, dielectric, Ag, wavelengths, lmax) # Figure 1: Scattering and Absorption fig, ax1 = plt.subplots() C = core_shell.cross_sections() plt.plot(wavelengths*1e9, C.scattering, label="Scattering", linewidth=2) plt.plot(wavelengths*1e9, C.absorption, label="Absorption", linewidth=2) # Figure 2: Scattering per multipole fig, ax2 = plt.subplots() plt.plot(wavelengths*1e9, C.scattering, label="Total", linewidth=2)
import miepy S = miepy.spheroid([0, 0, 0], 1, 1, miepy.constant_material(2)) T = S.compute_tmatrix(2, 5, 1) print(T)
def test_medium_cross_sections(plot=False): """verify cross-sections of an Au dimer in water by comparing with Poynting vector approach""" Nwav = 5 Au = miepy.materials.Au() radius = 50 * nm lmax = 2 nb = 1.33 medium = miepy.constant_material(nb**2) wavelengths = np.linspace(500 * nm, 1000 * nm, Nwav) separation = 2 * radius + 40 * nm source = miepy.sources.plane_wave.from_string(polarization='x') THETA, PHI = miepy.coordinates.sphere_mesh(20) R = (separation / 2 + radius + 20 * nm) * np.ones_like(THETA) X, Y, Z = miepy.coordinates.sph_to_cart(R, THETA, PHI) scat, absorb, extinct, C, A = (np.zeros_like(wavelengths) for i in range(5)) for i, wavelength in enumerate(wavelengths): sol = miepy.sphere_cluster(position=[[separation / 2, 0, 0], [-separation / 2, 0, 0]], radius=radius, material=Au, source=source, medium=medium, wavelength=wavelength, lmax=lmax) scat[i], absorb[i], extinct[i] = sol.cross_sections() E = sol.E_field(X, Y, Z, source=False) H = sol.H_field(X, Y, Z, source=False) C[i] = miepy.flux.flux_from_poynting_sphere(E, H, R, eps=nb**2) E = sol.E_field(X, Y, Z, source=True) H = sol.H_field(X, Y, Z, source=True) A[i] = -miepy.flux.flux_from_poynting_sphere(E, H, R, eps=nb**2) if not plot: for a, b, tol in [(C, scat, 1e-3), (A, absorb, 1e-3), (C + A, extinct, 1e-3)]: L2 = np.linalg.norm(a - b) / a.shape[0] avg = np.average(a + b) / 2 print(L2, avg) assert L2 < tol * avg else: plt.figure() line, = plt.plot(wavelengths / nm, C, 'o', color='C0', label='scattering (Poynting)') line, = plt.plot(wavelengths / nm, A, 'o', color='C1', label='absorbption (Poynting)') line, = plt.plot(wavelengths / nm, C + A, 'o', color='C2', label='extinction (Poynting)') plt.axhline(color='black', linestyle='--') line, = plt.plot(wavelengths / nm, scat, color='C0', label='scattering (analytic)') line, = plt.plot(wavelengths / nm, absorb, color='C1', label='absorbption (analytic)') line, = plt.plot(wavelengths / nm, extinct, color='C2', label='extinction (analytic)') plt.xlabel('wavelength (nm)') plt.ylabel(r'cross-section ($\mu$m$^2$)') plt.legend() plt.title(test_medium_cross_sections.__name__, weight='bold')
def vis(): nm = 1e-9 ### forces fig, axes = plt.subplots(nrows=2, figsize=(7, 6), sharex=True, gridspec_kw=dict(height_ratios=[2, 1], hspace=0.05)) norm = job.load(dimer_norm) scat = job.load(dimer_scat) for ax in axes: ax.plot((1 / nm) / norm.frequency, scat.Fx / norm.incident * norm.area * constants.epsilon_0 / 2 * 1e25, 'o', color='C0', label='Fx (FDTD)') ax.plot((1 / nm) / norm.frequency, scat.Fy / norm.incident * norm.area * constants.epsilon_0 / 2 * 1e25, 'o', color='C1', label='Fy (FDTD)') ax.plot((1 / nm) / norm.frequency, scat.Fz / norm.incident * norm.area * constants.epsilon_0 / 2 * 1e25, 'o', color='C2', label='Fz (FDTD)') import miepy wavelengths = np.linspace(400 * nm, 1000 * nm, 100) eps = meep_ext.get_eps(gold)(wavelengths) Au = miepy.data_material(wavelengths, eps * scale**2) water = miepy.constant_material(nb**2) # Au = miepy.constant_material(3.5**2) spheres = miepy.spheres( [[-sep / 2 / scale, 0, 0], [sep / 2 / scale, 0, 0]], radius / scale, Au) source = miepy.sources.rhc_polarized_plane_wave() sol = miepy.gmt(spheres, source, wavelengths, 2, medium=water) F = sol.force_on_particle(1) for ax in axes: ax.axhline(0, linestyle='--', color='black') ax.plot(wavelengths / nm, F[0] * 1e25, color='C0', label='Fx (GMT)') ax.plot(wavelengths / nm, F[1] * 1e25, color='C1', label='Fy (GMT)') ax.plot(wavelengths / nm, F[2] * 1e25, color='C2', label='Fz (GMT)') axes[0].legend() axes[0].set(ylabel='force') axes[1].set(xlabel='wavelength (nm)', ylabel='force', ylim=[-0.035, 0.01]) ### field animation fig, ax = plt.subplots() x = np.linspace(0, cell[0] / nm, Nx) z = np.linspace(0, cell[1] / nm, Nz) X, Z = np.meshgrid(x, z, indexing='ij') var = job.load(dimer_fields) idx = np.s_[10:-10, 10:-10] E = var.E[:, 10:-10, 10:-10] # E = var.E vmax = np.max(np.abs(E)) / 2 im = ax.pcolormesh(X[idx], Z[idx], E[0], cmap='RdBu', animated=True, vmax=vmax, vmin=-vmax) ax.set_aspect('equal') def update(i): im.set_array(np.ravel(E[i][:-1, :-1])) return [im] ani = animation.FuncAnimation(fig, update, range(E.shape[0]), interval=50, blit=True) plt.show()
""" Far-field analysis with the GMT """ import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D import matplotlib as mpl import miepy nm = 1e-9 sol = miepy.sphere_cluster( position=[[-100 * nm, 0, 0], [100 * nm, 0, 0]], radius=75 * nm, material=miepy.constant_material(3.6**2), source=miepy.sources.plane_wave.from_string(polarization='y'), wavelength=600 * nm, lmax=2) ### xy plane far-field fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}) r = 10000 * nm phi = np.linspace(0, 2 * np.pi, 100) theta = np.pi / 2 THETA, PHI = np.meshgrid(theta, phi, indexing='ij') E = sol.E_angular(THETA, PHI).squeeze() I = np.sum(np.abs(E)**2, axis=0)