Band structure¶
The unit of the array example, with lossy spheres of
permittivity 4 + 0.1i, repeats along z with a period of 210 nm: the 10 nm
slab, a 100 nm gap, the plane of sphere centers and another 100 nm gap. A
Bloch mode of this stack repeats from one period to the next up to a factor
exp(i kz a), where kz is its Bloch wavenumber and a = 210 nm the period.
bands finds these wavenumbers from the S-matrix of one period. The table
lists kz a / π at normal incidence for the modes with |Im| < 0.1, that is,
for the modes that decay slowly along the stack. The modes come in pairs
±kz, one for each direction. Lengths are in nm; the frequency column is
k0 / 2π, the inverse vacuum wavelength in 1/nm.
"""A periodic stack of sphere arrays on slabs: its Bloch modes along z."""
import numpy as np
import treams_rs as tr
# Enlarge for a full band diagram: treams uses 200 frequencies.
FREQUENCIES = 6
k0s = 2 * np.pi * np.linspace(1 / 10_000, 1 / 350, FREQUENCIES)
material_slab = 3
thickness = 10
material_sphere = tr.Material(4 + 0.1j, 1, 0.05)
lattice = tr.Lattice.square(500)
radius = 100
lmax = 3
# Period of the stack along z.
az = 210
# One array per frequency: Bloch wavenumbers kz in units of pi / az, kept
# where |Im| < 0.1, that is, for modes that decay slowly along the stack.
res = []
for k0 in k0s:
kpar = [0, 0]
sphere = tr.sphere_tmatrix(
k0=k0, lmax=lmax, radius=radius, material=material_sphere
)
spheres = tr.solve_periodic(sphere, lattice=lattice, kpar=kpar)
ports = tr.PlaneWavePorts.diffr_orders(kpar, lattice, 0.02)
slab = tr.slab(k0=k0, basis=ports, thickness=thickness, material=material_slab)
gap = tr.propagation(k0=k0, basis=ports, distance=radius)
array = spheres.to_smatrix(ports)
# One period: slab, gap, sphere centers, and the gap to the next slab.
cell = tr.stack([slab, gap, array, gap])
x = cell.bands(period=az).wavenumbers * az / np.pi
# Eigenvalue solvers return the modes in no fixed order; sort them.
res.append(np.sort_complex(x[np.abs(np.imag(x)) < 0.1]))
if __name__ == "__main__":
print("Bloch wavenumbers kz az / pi with |Im| < 0.1")
print(f"{'k0/(2 pi)':>13}{'Re':>13}{'Im':>13}")
for k0, values in zip(k0s, res, strict=True):
for value in values:
print(f"{k0 / (2 * np.pi):>13.6g}{value.real:>13.6g}{value.imag:>13.6g}")
# Source: https://github.com/tfp-photonics/treams/blob/1f5d0d6ebb007288f28bc9e16f6d266e8b55dc39/docs/examples/band_structure.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 / 10_000, 1 / 350, 6)
material_slab = 3
thickness = 10
material_sphere = treams.Material(4 + 0.1j, 1, 0.05)
lattice = treams.Lattice.square(500)
radius = 100
lmax = 3
az = 210
res = []
for k0 in k0s:
kpar = [0, 0]
spheres = treams.TMatrix.sphere(
lmax, k0, radius, [material_sphere, 1]
).latticeinteraction.solve(lattice, kpar)
pwb = treams.PlaneWaveBasisByComp.diffr_orders(kpar, lattice, 0.02)
plw = treams.plane_wave(kpar, [1, 0, 0], k0=k0, basis=pwb, material=1)
slab = treams.SMatrices.slab(thickness, pwb, k0, [1, material_slab, 1])
dist = treams.SMatrices.propagation([0, 0, radius], pwb, k0, 1)
array = treams.SMatrices.from_array(spheres, pwb)
total = treams.SMatrices.stack([slab, dist, array, dist])
x, _ = total.bands_kz(az)
x = x * az / np.pi
sel = x[np.abs(np.imag(x)) < 0.1]
res.append(sel)
Output¶
Bloch wavenumbers kz az / pi with |Im| < 0.1
k0/(2 pi) Re Im
0.0001 -0.0463046 -3.44198e-05
0.0001 -0.0461419 -3.72028e-05
0.0001 0.0461419 3.72028e-05
0.0001 0.0463046 3.44198e-05
0.000651429 -0.302842 -0.000268014
0.000651429 -0.3016 -0.000283914
0.000651429 0.3016 0.000283914
0.000651429 0.302842 0.000268014
0.00120286 -0.56635 -0.000811572
0.00120286 -0.562797 -0.000808936
0.00120286 0.562797 0.000808936
0.00120286 0.56635 0.000811572
0.00175429 -0.855806 -0.00313047
0.00175429 -0.843723 -0.00273337
0.00175429 -0.348999 -0.0068234
0.00175429 -0.0280163 -0.0941962
0.00175429 0.0280163 0.0941962
0.00175429 0.348999 0.0068234
0.00175429 0.843723 0.00273337
0.00175429 0.855806 0.00313047
0.00230571 -0.86705 -0.00732377
0.00230571 -0.838782 -0.00779511
0.00230571 -0.817621 -0.0128551
0.00230571 -0.801108 0.0120328
0.00230571 -0.781632 -0.0110793
0.00230571 -0.742894 0.0187004
0.00230571 -0.629712 -0.00815674
0.00230571 -0.627992 -0.00123334
0.00230571 -0.612331 -0.00392768
0.00230571 -0.563723 -0.00133275
0.00230571 0.563723 0.00133275
0.00230571 0.612331 0.00392768
0.00230571 0.627992 0.00123334
0.00230571 0.629712 0.00815674
0.00230571 0.742894 -0.0187004
0.00230571 0.781632 0.0110793
0.00230571 0.801108 -0.0120328
0.00230571 0.817621 0.0128551
0.00230571 0.838782 0.00779511
0.00230571 0.86705 0.00732377
0.00285714 -0.998195 0.0528495
0.00285714 -0.997536 0.0433105
0.00285714 -0.995829 -0.0524251
0.00285714 -0.99351 -0.0287446
0.00285714 -0.86054 0.00796757
0.00285714 -0.830748 0.00342023
0.00285714 -0.817856 0.00309955
0.00285714 -0.658499 -0.00536573
0.00285714 -0.637574 -0.00510091
0.00285714 -0.606323 -0.00513062
0.00285714 -0.587249 -0.00357882
0.00285714 -0.541919 -0.010382
0.00285714 -0.500106 -0.0243015
0.00285714 -0.464966 -0.0095632
0.00285714 -0.433045 0.0399034
0.00285714 -0.320497 0.0269757
0.00285714 0.320497 -0.0269757
0.00285714 0.433045 -0.0399034
0.00285714 0.464966 0.0095632
0.00285714 0.500106 0.0243015
0.00285714 0.541919 0.010382
0.00285714 0.587249 0.00357882
0.00285714 0.606323 0.00513062
0.00285714 0.637574 0.00510091
0.00285714 0.658499 0.00536573
0.00285714 0.817856 -0.00309955
0.00285714 0.830748 -0.00342023
0.00285714 0.86054 -0.00796757
0.00285714 0.99351 0.0287446
0.00285714 0.995829 0.0524251
0.00285714 0.997536 -0.0433105
0.00285714 0.998195 -0.0528495
Differences from the treams version¶
bands(period=az)returns the named fieldswavenumbersandeigenvectors; treams'bands_kz(az)returns a pair. treams-rs also hasbands_kz.- The script sorts the modes of each frequency, so the table has a fixed order. The two codes return the eigenvalues in different orders.
- The treams version also builds a plane wave that it never uses; the treams-rs script leaves it out.