Single sphere¶
A sphere with permittivity 16 + 0.5i and radius 75 nm has electric and magnetic multipole resonances between 430 and 1000 THz. The spectrum shows the extinction and scattering efficiencies, the cross sections averaged over all directions and polarizations of the incident light and divided by the geometric cross section πr². Keeping only the dipole modes (l = 1) shows which resonances higher multipoles cause. A plane wave at 1000 THz then gives the intensity of the total field around the sphere.
"""Extinction and scattering spectrum of a sphere and the intensity around it."""
import numpy as np
import treams_rs as tr
# Enlarge these for full sweeps: treams uses 200 frequencies and a 101 x 101 grid.
FREQUENCIES = 6
GRID_POINTS = 7
k0s = 2 * np.pi * np.linspace(1 / 700, 1 / 300, FREQUENCIES)
material = tr.Material(16 + 0.5j)
lmax = 4
radius = 75
spheres = [
tr.sphere_tmatrix(k0=k0, lmax=lmax, radius=radius, material=material) for k0 in k0s
]
# Efficiencies are cross sections divided by the geometric cross section.
area = np.pi * radius**2
averages = [tm.average_cross_sections for tm in spheres]
xs_sca = np.array([avg.scattering for avg in averages]) / area
xs_ext = np.array([avg.extinction for avg in averages]) / area
# Keep only the dipole modes (l = 1) of the same T-matrices.
dipoles = tr.SphericalBasis.default(1)
averages_lmax1 = [tm.select(dipoles).average_cross_sections for tm in spheres]
xs_sca_lmax1 = np.array([avg.scattering for avg in averages_lmax1]) / area
xs_ext_lmax1 = np.array([avg.extinction for avg in averages_lmax1]) / area
# Total field of a plane wave at the highest frequency, in the plane y = 0.
tm = spheres[-1]
inc = tr.plane_wave(direction=[0, 0, 1], pol="positive_helicity", k0=tm.k0)
sca = tm.scatter(inc)
x = np.linspace(-100, 100, GRID_POINTS)
z = np.linspace(-100, 100, GRID_POINTS)
xx, zz = np.meshgrid(x, z, indexing="ij")
grid = np.stack((xx, np.zeros_like(xx), zz), axis=-1)
# Points inside the sphere get NaN: the expansion holds only outside it.
intensity = np.full_like(xx, np.nan)
valid = tm.valid_points(grid, [radius])
total = inc.efield(grid[valid]) + sca.efield(grid[valid])
intensity[valid] = 0.5 * np.sum(np.abs(total) ** 2, axis=-1)
if __name__ == "__main__":
print("Efficiencies (cross section / geometric cross section)")
headers = ("frequency/THz", "extinction", "scattering", "ext. l=1", "sca. l=1")
print("".join(f"{h:>15}" for h in headers))
frequencies = k0s * 299792.458 / (2 * np.pi)
columns = (frequencies, xs_ext, xs_sca, xs_ext_lmax1, xs_sca_lmax1)
for row in zip(*columns, strict=True):
print("".join(f"{v:>15.6g}" for v in row))
print("\nIntensity at y = 0 (rows: x, columns: z)")
for row in intensity:
print("".join(f"{v:>11.6g}" for v in row))
# Source: https://github.com/tfp-photonics/treams/blob/1f5d0d6ebb007288f28bc9e16f6d266e8b55dc39/docs/examples/sphere.py
# Adapted: reduced sizes, plotting removed.
# MIT license, copyright Dominik Beutel; see LICENSE.treams.
import numpy as np
import treams
k0s = 2 * np.pi * np.linspace(1 / 700, 1 / 300, 6)
materials = [treams.Material(16 + 0.5j), treams.Material()]
lmax = 4
radius = 75
spheres = [treams.TMatrix.sphere(lmax, k0, radius, materials) for k0 in k0s]
xs_sca = np.array([tm.xs_sca_avg for tm in spheres]) / (np.pi * radius**2)
xs_ext = np.array([tm.xs_ext_avg for tm in spheres]) / (np.pi * radius**2)
swb_lmax1 = treams.SphericalWaveBasis.default(1)
spheres_lmax1 = [tm[swb_lmax1] for tm in spheres]
xs_sca_lmax1 = np.array([tm.xs_sca_avg for tm in spheres_lmax1]) / (np.pi * radius**2)
xs_ext_lmax1 = np.array([tm.xs_ext_avg for tm in spheres_lmax1]) / (np.pi * radius**2)
tm = spheres[-1]
inc = treams.plane_wave([0, 0, tm.k0], 1, k0=tm.k0, material=tm.material)
sca = tm @ inc.expand(tm.basis)
x = np.linspace(-100, 100, 7)
y = 0
z = np.linspace(-100, 100, 7)
xx, zz = np.meshgrid(x, z, indexing="ij")
yy = np.full_like(xx, y)
grid = np.stack((xx, yy, zz), axis=-1)
intensity = np.zeros_like(xx)
valid = tm.valid_points(grid, [radius])
intensity[~valid] = np.nan
intensity[valid] = 0.5 * np.sum(
np.abs(inc.efield(grid[valid]) + sca.efield(grid[valid])) ** 2, -1
)
Output¶
Efficiencies (cross section / geometric cross section)
frequency/THz extinction scattering ext. l=1 sca. l=1
428.275 1.33296 0.987177 1.33118 0.986812
542.482 3.51292 3.10639 3.50198 3.10339
656.688 5.39143 4.3844 5.15451 4.32194
770.895 1.02102 0.60478 0.766772 0.467907
885.102 3.04969 1.59416 1.23458 1.02832
999.308 3.23746 2.55864 3.02429 2.47346
Intensity at y = 0 (rows: x, columns: z)
0.532304 0.332909 0.305677 0.437686 0.456637 0.408559 0.393593
0.394684 0.209014 nan nan nan 0.684083 0.539167
0.256366 nan nan nan nan nan 0.594802
0.190624 nan nan nan nan nan 0.54269
0.256366 nan nan nan nan nan 0.594802
0.394684 0.209014 nan nan nan 0.684083 0.539167
0.532304 0.332909 0.305677 0.437686 0.456637 0.408559 0.393593
Differences from the treams version¶
sphere_tmatrixtakes keywords and the sphere'smaterial; the exteriormediumis vacuum unless given. treams takes one list of materials, ending with the exterior.average_cross_sectionsreturns namedscatteringandextinctionvalues; treams readsxs_sca_avgandxs_ext_avg.select(SphericalBasis.default(1))keeps the dipole modes; treams indexes withtm[swb_lmax1].- The script gives
plane_wavea unitdirection, which does not depend on k0; treams passes the wave vector[0, 0, k0], which treams-rs also accepts. scatterreturns the scattered wave; treams writestm @ inc.expand(tm.basis).- The intensity map starts as NaN, so points inside the sphere stay NaN.