Skip to content

Commit

Permalink
Tutorial for phase of mode coefficients (#2433)
Browse files Browse the repository at this point in the history
* tutorial for phase of mode coefficients

* add text

* updates to figure and text

* provide more details on necessity of factor of 2 in front of L in phase correction factor
  • Loading branch information
oskooi authored Mar 16, 2023
1 parent c376694 commit 75ffb03
Show file tree
Hide file tree
Showing 3 changed files with 497 additions and 0 deletions.
258 changes: 258 additions & 0 deletions doc/docs/Python_Tutorials/Mode_Decomposition.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,264 @@ The reflectance values computed using the two methods are nearly identical. For

In the reflected-flux calculation, we apply our usual trick of first performing a reference simulation with just the incident field and then subtracting that from our taper simulation with `load_minus_flux_data`, so that what is left over is the reflected fields (from which we obtain the reflected flux). In *principle*, this trick would not be required for the mode-decomposition method, because the reflected mode is orthogonal to the forward mode and so the decomposition will separate the forward and reflected coefficients automatically. However, this is only true in the limit of infinite resolution — for a *finite* resolution, the reflected mode used for the mode coefficient calculation (calculated via MPB) is not exactly orthogonal to the forward mode propagating in Meep (whose discretization scheme is different from that of MPB). In consequence, if you did not subtract the fields of the reference simulation, the mode-coefficient could only calculate the reflected power down to a "noise floor" set by the discretization error. With the subtraction, in contrast, you can compute much smaller reflections (limited by the floating-point precision).

Phase of a Total Internal Reflected Mode
----------------------------------------

For certain applications, such as [physically based ray tracing](https://pbr-book.org/), the phase of the reflection/transmission coefficient of a surface interface (flat or textured) is an important parameter used for modeling coherent effects (i.e., constructive or destructive interference from a collection of intersecting rays scattered off the surface). This tutorial demonstrates how to use mode decomposition to compute the phase of the reflection coefficient of a [total internal reflected](https://en.wikipedia.org/wiki/Total_internal_reflection) (TIR) mode. The simulated results are verified using the [Fresnel equations](https://en.wikipedia.org/wiki/Total_internal_reflection#Phase_shifts).

The example involves a 2d simulation of a flat interface of two lossless media with $n_1=1.5$ and $n_2=1.0$. The [critical angle](https://en.wikipedia.org/wiki/Total_internal_reflection#Critical_angle) for this interface is $\theta_c = 41.8^\circ$. The source is an incident planewave with linear polarization ($S$ or $P$). The 2d cell is periodic in $y$ with PML in the $x$ direction. The cell size does not affect the results and is therefore arbitrary. A schematic of the simulation layout is shown below.

The key item to consider in these types of calculations is the *location of the mode monitor relative to the interface*. The mode monitor is a line extending the entire length of the cell in the $y$ direction. In order to compare the result with the Fresnel equations, the phase of the reflection coefficient must be computed *exactly* at the interface. However, it is problematic to measure the reflection coefficient exactly at the interface because the amplitude of the left-going wave drops discontinuously to zero across the interface. For this reason, the mode monitor must be positioned away from the interface (somewhere within the $n_1$ medium) and the measured phase must be corrected in post processing to account for this offset.

![](../images/refl_coeff_flat_interface.png#center)

The calculation of the reflection coefficient requires two separate runs: (1) a normalization run involving just the source medium ($n_1$) to obtain the incident fields, and (2) the main run including the interface whereby the incident fields from (1) are first subtracted from the monitor to obtain only the reflected fields. The mode coefficient in (2) divided by (1) is, by definition, the reflection coefficient.

The phase of the reflection coefficient needs to be corrected for the offset of the mode monitor relative to the interface — labeled $L$ in the schematic above — using the formula $\exp(i 2\pi k_x 2L)$, where $k_x$ is the $x$ component of the planewave's wavevector (`k` in the script). The $2\pi$ factor is necessary because `k` does *not* include this factor (per convention in Meep). The factor 2 in front of the $L$ is necessary because the phase needs to be corrected for the monitors in the normalization and main runs separately. The correction factor is just the phase accumulated as the wave propagates in the surface-normal direction for a distance $L$ in a medium with index $n_1$.

With this setup, we measure the phase of the reflection coefficient for two different source configurations (polarization and angle) and compare the result with the Fresnel equations. The location of the mode monitor is also varied in the two test configurations. Results are shown in the two tables below. There is good agreement between the simulation and theory. (As additional validation, we note that the magnitude of the reflection coefficient of a TIR mode must be one which is indeed the case.)

| $S$ pol., $\theta$ = 54.3° | reflection coefficient | phase (radians) |
|:--------------------------:|:----------------------:|:---------------:|
| Meep | 0.23415-0.96597j | -1.33299 |
| Fresnel | 0.22587-0.97416j | -1.34296 |


| $P$ pol., $\theta$ = 48.5° | reflection coefficient | phase (radians) |
|:--------------------------:|:----------------------:|:---------------:|
| Meep | 0.11923+0.98495j | 1.45033 |
| Fresnel | 0.14645+0.98922j | 1.42382 |

The simulation script is in [examples/mode_coeff_phase.py](https://github.com/NanoComp/meep/blob/master/python/examples/mode_coeff_phase.py).

```py
import cmath
from enum import Enum
import math

import meep as mp
import numpy as np

Polarization = Enum("Polarization", "S P")

# refractive indices of materials 1 and 2
n1 = 1.5
n2 = 1.0


def refl_coeff_meep(pol: Polarization, theta: float, L: float) -> complex:
"""Computes the reflection coefficient of a TIR mode using mode
decomposition.
Args:
pol: polarization of the incident planewave (S or P).
theta: angle of the incident planewave (radians).
L: position of the mode monitor relative to the flat interface. 0 is
interface position.
"""
resolution = 50.0

# cell size is arbitrary
sx = 7.0
sy = 3.0
dpml = 2.0
cell_size = mp.Vector3(sx + 2 * dpml, sy, 0)
pml_layers = [mp.PML(dpml, direction=mp.X)]

fcen = 1.0 # center frequency
df = 0.1 * fcen

# k (in source medium) with correct length
# plane of incidence is xy
k = mp.Vector3(n1 * fcen, 0, 0).rotate(mp.Vector3(0, 0, 1), theta)

# planewave amplitude function (for source)
def pw_amp(k, x0):
def _pw_amp(x):
return cmath.exp(1j * 2 * math.pi * k.dot(x + x0))

return _pw_amp

src_pt = mp.Vector3(-0.5 * sx, 0, 0)

if pol.name == "S":
src_cmpt = mp.Ez
eig_parity = mp.ODD_Z
elif pol.name == "P":
src_cmpt = mp.Hz
eig_parity = mp.EVEN_Z
else:
raise ValueError("pol must be S or P, only.")

sources = [
mp.Source(
mp.GaussianSource(fcen, fwidth=df),
component=src_cmpt,
center=src_pt,
size=mp.Vector3(0, cell_size.y, 0),
amp_func=pw_amp(k, src_pt),
),
]

sim = mp.Simulation(
resolution=resolution,
cell_size=cell_size,
default_material=mp.Medium(index=n1),
boundary_layers=pml_layers,
k_point=k,
sources=sources,
)

# DFT monitor for incident fields
mode_mon = sim.add_mode_monitor(
fcen,
0,
1,
mp.FluxRegion(
center=mp.Vector3(-L, 0, 0),
size=mp.Vector3(0, cell_size.y, 0),
),
)

sim.run(
until_after_sources=mp.stop_when_fields_decayed(
50,
src_cmpt,
mp.Vector3(-L, 0, 0),
1e-6,
),
)

res = sim.get_eigenmode_coefficients(
mode_mon,
bands=[1],
eig_parity=eig_parity,
kpoint_func=lambda *not_used: k,
direction=mp.NO_DIRECTION,
)

input_mode_coeff = res.alpha[0, 0, 0]
input_flux_data = sim.get_flux_data(mode_mon)

sim.reset_meep()

geometry = [
mp.Block(
material=mp.Medium(index=n1),
center=mp.Vector3(-0.25 * (sx + 2 * dpml), 0, 0),
size=mp.Vector3(0.5 * (sx + 2 * dpml), mp.inf, mp.inf),
),
mp.Block(
material=mp.Medium(index=n2),
center=mp.Vector3(0.25 * (sx + 2 * dpml), 0, 0),
size=mp.Vector3(0.5 * (sx + 2 * dpml), mp.inf, mp.inf),
),
]

sim = mp.Simulation(
resolution=resolution,
cell_size=cell_size,
boundary_layers=pml_layers,
k_point=k,
sources=sources,
geometry=geometry,
)

# DFT monitor for reflected fields
mode_mon = sim.add_mode_monitor(
fcen,
0,
1,
mp.FluxRegion(
center=mp.Vector3(-L, 0, 0),
size=mp.Vector3(0, cell_size.y, 0),
),
)

sim.load_minus_flux_data(mode_mon, input_flux_data)

sim.run(
until_after_sources=mp.stop_when_fields_decayed(
50,
mp.Ez,
mp.Vector3(-L, 0, 0),
1e-6,
),
)

res = sim.get_eigenmode_coefficients(
mode_mon,
bands=[1],
eig_parity=eig_parity,
kpoint_func=lambda *not_used: k,
direction=mp.NO_DIRECTION,
)

# mode coefficient of reflected planewave
refl_mode_coeff = res.alpha[0, 0, 1]

# reflection coefficient
refl_coeff = refl_mode_coeff / input_mode_coeff

# apply phase correction factor
refl_coeff /= cmath.exp(1j * k.x * 2 * math.pi * 2 * L)

return refl_coeff


def refl_coeff_Fresnel(pol: Polarization, theta: float) -> complex:
"""Computes the reflection coefficient of a TIR mode using the Fresnel
equations.
Args:
pol: polarization of the incident planewave (S or P).
theta: angle of the incident planewave (degrees).
"""
if pol.name == "S":
refl_coeff = (
math.cos(theta) - ((n2 / n1) ** 2 - math.sin(theta) ** 2) ** 0.5
) / (math.cos(theta) + ((n2 / n1) ** 2 - math.sin(theta) ** 2) ** 0.5)
else:
refl_coeff = (
-((n2 / n1) ** 2) * math.cos(theta)
+ ((n2 / n1) ** 2 - math.sin(theta) ** 2) ** 0.5
) / (
(n2 / n1) ** 2 * math.cos(theta)
+ ((n2 / n1) ** 2 - math.sin(theta) ** 2) ** 0.5
)

return refl_coeff


if __name__ == "__main__":
thetas = [54.3, 48.5] # angle of incident planewave (degrees)
Ls = [0.4, 1.2] # position of mode monitor relative to flat interface
pols = [Polarization.S, Polarization.P] # polarization of incident planewave

for pol, theta, L in zip(pols, thetas, Ls):
theta_rad = np.radians(theta)
rc_m = refl_coeff_meep(pol, theta_rad, L)
rc_f = refl_coeff_Fresnel(pol, theta_rad)

rc_m_str = f"{rc_m.real:.5f}{rc_m.imag:+.5f}j"
rc_f_str = f"{rc_f.real:.5f}{rc_f.imag:+.5f}j"
print(f"refl-coeff:, {pol.name}, {theta}, {rc_m_str} (Meep), "
f"{rc_f_str} (Fresnel)")

mag_m = abs(rc_m)
mag_f = abs(rc_f)
err_mag = abs(mag_m - mag_f) / mag_f
print(f"magnitude:, {mag_m:.5f} (Meep), {mag_f:.5f} (Fresnel), "
f"{err_mag:.5f} (error)")

phase_m = cmath.phase(rc_m)
phase_f = cmath.phase(rc_f)
err_phase = abs(phase_m - phase_f) / abs(phase_f)
print(f"phase:, {phase_m:.5f} (Meep), {phase_f:.5f} (Fresnel), "
f"{err_phase:.5f} (error)")
```


Diffraction Spectrum of a Binary Grating
----------------------------------------

Expand Down
Binary file added doc/docs/images/refl_coeff_flat_interface.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 75ffb03

Please sign in to comment.