Guided mode expansion and polarization mixing

This example is related to Sec. 4.2 of the CPC paper. We calculate the bands of a PhC slab in the presence of vertical mirror symmetry and we wish to answer the following question: if a quasi-guided PhC slab mode is even (odd) under reflection in a vertical mirror plane, does it couple only to p-polarized (s-polarized) radiation modes in the far field?

We answer this question by decomposing the imaginary part of the frequency into the contributions arising from s-polarized or p-polarized plane waves. This feature is new to Legume 1.0 (2024 version, CPC paper).

[1]:
import legume
print(f"Version of the imported legume : {legume.__version__}")

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mpc

import copy
Version of the imported legume : 1.0.0

Define the PhC

We adopt the parameters of Fig. 8 of the CPC paper, but we employ dimensionless unit.

[2]:
D = 0.2 # slab thickness in units of a
r = 0.25  # hole radius in units of a
eps_c = 1          # permittivity of circular holes
eps_b = 3.45**2    # background permittivity of slab
eps_lower, eps_upper = 1, 1  # permittivities of lower and upper claddings


lattice = legume.Lattice('square')
phc = legume.PhotCryst(lattice, eps_l=eps_lower, eps_u=eps_upper)
phc.add_layer(d=D, eps_b=eps_b)
phc.layers[-1].add_shape(legume.Circle(eps=eps_c, r=r, x_cent=0., y_cent=0))

gme = legume.GuidedModeExp(phc, gmax=4.5, truncate_g='abs')
npw = np.shape(gme.gvec)[1]  # number of plane waves in the expansion
print(f'Number of reciprocal lattice vectors in the expansion: npw = {npw}')
Number of reciprocal lattice vectors in the expansion: npw = 69

Calculate \(s-\) and \(p-\) farfield coupling

For simplicity, we consider only \(xy\)-even modes, adopting a basis of guided modes \(\alpha=0,3\).

[3]:
nk1, nk2 = 20, 30
path = lattice.bz_path(['X', 'G', 'M'], [nk1, nk2])
gmode_inds, numeig = [0, 3], 30
verbose, compute_im = True, True
nkappa = path['kpoints'].shape[1]

gme = legume.GuidedModeExp(phc, gmax=4, truncate_g='abs')
gme.run(kpoints=path['kpoints'], angles=path['angles'], gmode_inds=gmode_inds, kz_symmetry='even',
        numeig=numeig, verbose=verbose, compute_im=compute_im)
freqs = gme.freqs
freqs_im = gme.freqs_im
freqs_im_s = gme.freqs_im*gme.unbalance_sp
freqs_im_p = gme.freqs_im*(1-gme.unbalance_sp)
Warning: gmax=4 exactly equal to one of the g-vectors modulus, reciprocal lattice truncated with gmax=4.0000000001 to avoid problems.
Plane waves used in the expansion = 49.
Running gme k-points: │██████████████████████████████│ 51 of 51
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Steps in GuidedModeExp: 49 plane waves and 2 guided modes  Time (s)                  % vs total T ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Guided modes computation with gmode_compute='exact'        1.526     │███████████████-----│   75% │
│ Inverse matrix of Fourier-space permittivity               0.017     │--------------------│    1% │
│ Matrix diagionalization using the 'eigh' solver            0.028     │--------------------│    1% │
│ Creating GME matrix                                        0.241     │██------------------│   12% │
│ Creating change of basis matrix using dense matrices       0.177     │█-------------------│    9% │
├───────────────────────────────────────────────────────────┼──────────┼──────────────────────────────┤
│ Total time for real part of frequencies for 51 k-points    2.025     │████████████████████│  100% │
└───────────────────────────────────────────────────────────┴──────────┴──────────────────────────────┘
Running GME losses k-point: │██████████████████████████████│ 51 of 51
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┓
┃ Steps in GuidedModeExp: 49 plane waves and 2 guided modes         Time (s) ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━┩
│ Total time for imaginary part of frequencies for 1530 eigenmodes  5.568    │
└──────────────────────────────────────────────────────────────────┴──────────┘

Prepares the light lines for the plot

[4]:
eps_clad = [gme.phc.claddings[0].eps_avg, gme.phc.claddings[-1].eps_avg]

# this is the usual light line for the cladding
light = np.sqrt(
        np.square(gme.kpoints[0, :]) +
        np.square(gme.kpoints[1, :])) / 2 / np.pi / np.sqrt(max(eps_clad))

# this are light lines folded with various reciprocal lattice vectors
Grec1  = [0, -2*np.pi]  # this in particular gives the cutoff for out-of-plane diffraction
light1 = np.sqrt(
         np.square(gme.kpoints[0, :]+Grec1[0]) +
         np.square(gme.kpoints[1, :]+Grec1[1])) / 2 / np.pi / np.sqrt(max(eps_clad))

Grec2  = [-2*np.pi, 0]
light2 = np.sqrt(
         np.square(gme.kpoints[0, :]+Grec2[0]) +
         np.square(gme.kpoints[1, :]+Grec2[1])) / 2 / np.pi / np.sqrt(max(eps_clad))

Grec3  = [-2*np.pi, -2*np.pi]
light3 = np.sqrt(
         np.square(gme.kpoints[0, :]+Grec3[0]) +
         np.square(gme.kpoints[1, :]+Grec3[1])) / 2 / np.pi / np.sqrt(max(eps_clad))

Plot the results

[5]:
nfreqs = freqs.shape[1]
ymin, ymax = 0.4, 1.4

markersize = 20
vmin, vmax = 1e-6, 1e-2
conecolor ='lightcyan'
conecolor1='cyan'

# we set a minimum value to the colorscale, otherwise the points below the line line do not appear
freqs_im_s_for_plot = copy.deepcopy(freqs_im_s)
freqs_im_p_for_plot = copy.deepcopy(freqs_im_p)
for i in range(nkappa):
  for j in range(nfreqs):
    freqs_im_s_for_plot[i,j] = max(freqs_im_s[i,j], vmin)
    freqs_im_p_for_plot[i,j] = max(freqs_im_p[i,j], vmin)


fig, ax = plt.subplots(nrows=1, ncols=2, constrained_layout=True, figsize=(6, 4))
kappa = range(nkappa)
for j in range(nfreqs):
#  ax.scatter(kappa, freqs_even[:,j], c='red', s=markersize)
  f0 = ax[0].scatter(kappa, freqs[:,j], c=freqs_im_s_for_plot[:,j],
             s=markersize, edgecolors='black', linewidths=0.5,
             norm=mpc.LogNorm(vmin= vmin, vmax=vmax), cmap='viridis_r')
  f1 = ax[1].scatter(kappa, freqs[:,j], c=freqs_im_p_for_plot[:,j],
             s=markersize, edgecolors='black', linewidths=0.5,
             norm=mpc.LogNorm(vmin= vmin, vmax=vmax), cmap='viridis_r')

for a in ax: # Loop over all axis for common set-up (limits, lightcones,...)
  a.set_ylim([ymin, ymax])
  a.set_xlim([0, nkappa-1])
  a.set_xlabel("Wavevector")
  a.set_xticks(path['indexes'], path['labels'])
  a.xaxis.grid('True')
  a.plot(kappa, light,  color='blue')
  a.plot(kappa, light2, color='green')
  a.plot(kappa, light1, color='red')
  a.plot(kappa, light3, color='magenta')
  a.fill_between(kappa, light,  max(100, light.max(), gme.freqs[:].max()),
                facecolor=conecolor,        zorder=0)
  a.fill_between(kappa, light1,  max(100, light.max(), gme.freqs[:].max()),
                facecolor=conecolor1,       zorder=0)

ax[0].set_title('coupling to s-pol')
ax[0].set_ylabel("$\omega a/2\pi c$")
ax[1].set_title('coupling to p-pol')

plt.colorbar(f1, ax=ax[1], label="$\Im(\omega a/2\pi c)$", extend="max")

[5]:
<matplotlib.colorbar.Colorbar at 0x7c4f2402a950>
../_images/examples_14_GME_polarization_mixing_11_1.png

Interpretation: * the blue line (light line in the cladding) determines the onset of losses of quasi-guided modes. Above this cutoff, kz-even modes start to couple to p-polarized plane waves, thus they become radiative - but there is no polarization mixing. * the red line (folded line corresponding to the reciprocal lattice vector [0, -2*np.pi]) is the cutoff for diffraction in directions out of the \({\bf k}z\) plane. Above this cutoff, kz-even modes start to couple to s-polarized plane waves - and there is polarization mixing!

Compare with Fig. 10(a,b) of the CPC paper.