# pylint: disable=invalid-name
# pylint: disable=too-many-arguments
# pylint: disable=consider-using-f-string
"""
Useful routines for step-index planar waveguides.
See <https://ofiber.readthedocs.io> for usage examples.
A step-index planar waveguide is a flat waveguide that consists of three layers.
Let z be the direction of light propagation through the waveguide. Let x be the
direction normal to the surface, and then the y-direction is parallel to the
planes of the waveguide.
The top and bottom layers are semi-infinite with constant index of refraction.
The middle layer has a fixed thickness and a fixed index of refraction. This
waveguide is slightly simpler mathematically to solve than the cylindrical
waveguide and serves as a useful preliminary model that exhibits general
behaviors found in most waveguides.
The functions fall into two classes, those for transverse electric fields (TE)
and those for transverse magnetic fields (TM). A transverse electric field
only has an electric field in the y-direction. Similarly the TM field only has
a magnetic field in the y-direction.
The transverse electric field routines are::
TE_crossing(V, mode)
TE_crossings(V)
TE_field(V, d, x, mode)
TE_mode_plot(V)
TE_propagation_constant(V, mode)
The transverse electric routines are::
TM_crossing(V, mode)
TM_crossings(V)
TM_field(V, n1, n2, d, x, mode)
TM_mode_plot(V)
TM_propagation_constant(V, mode, n1, n2)
Based on chapter 7 of Ghatak and Thyagarajan, *An Introduction to
Fiber Optics*, Cambridge University Press, 1998.
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import brentq
__all__ = ('TE_crossing',
'TE_crossings',
'TE_field',
'TE_mode_plot',
'TE_propagation_constant',
'TM_crossing',
'TM_crossings',
'TM_field',
'TM_mode_plot',
'TM_propagation_constant')
def _base_mode_plot(V):
"""
Plot the symmetric and asymmetric modes in a planar waveguide.
Args:
V: the V-parameter for the waveguide
Returns:
a matplotlib.pyplot object
"""
abit = 1e-5
_, ax = plt.subplots(figsize=(8, 8))
ax.set_aspect('equal')
xi = np.linspace(abit, np.pi / 2 - abit, 50)
while xi[0] < V / 2:
plt.plot(xi, xi * np.tan(xi), color='red')
xi += np.pi / 2
xi = np.linspace(np.pi / 2, np.pi - abit, 50)
while xi[0] < V / 2:
plt.plot(xi, -xi / np.tan(xi), '--b')
xi += np.pi
plt.xlabel(r'$\xi=(d/2)\sqrt{k_0^2n_1^2-\beta^2}=(d/2)\kappa$')
return plt
[docs]
def TE_mode_plot(V):
"""
Plot the symmetric and asymmetric TE modes in a planar waveguide.
Args:
V: the V-parameter for the waveguide
Returns:
a matplotlib.pyplot object
"""
abit = 1e-5
xi = np.linspace(0, V / 2 - abit, 100)
circle = np.sqrt((V / 2)**2 - xi**2)
aplt = _base_mode_plot(V)
aplt.plot(xi, circle, ':k')
ystr = r'$\xi\,\tan\xi$ or $-\xi\,\cot\xi$ or $\sqrt{V^2/4-\xi^2}$'
aplt.ylabel(ystr)
aplt.title('TE Modes in Planar Film Waveguide (V=%.2f)' % V)
aplt.ylim(0, V / 2 + 1)
aplt.xlim(0, V / 2 + 1)
return aplt
def _TE_mode(xi, *args):
"""
Return the value of TE eigenvalue equation.
The zeros of eigenvalue equation determine the eigenvalues which
in turn are needed to determine the propagation factor for a planar
waveguide.
The planar waveguide characteristics are passed as a 2-element array.
Args:
xi : non-dimensional propagation value in waveguide
arg[0]: the V-parameter for the waveguide
arg[1]: the desired mode (even values are symmetric)
Returns:
the distance from a solution
"""
V = args[0]
mode = args[1]
if mode % 2 == 0:
return xi * np.tan(xi) - np.sqrt(V**2 / 4 - xi * xi)
return xi / np.tan(xi) + np.sqrt(V**2 / 4 - xi * xi)
[docs]
def TE_crossing(V, mode):
"""
Return the TE eigenvalue for a planar waveguide mode.
The eigenvalue, xi, satisfies the eigenvalue equation for
the specified TE mode in a planar waveguide.
The solution is found numerically using the Brent method. The
only tricky bit is setting the proper range to search within.
Args:
V : the V-parameter for the waveguide
mode : the mode
Returns:
the eigenvalue. If no eigenvalue, 0 is returned.
"""
abit = 1e-5
lo = abit + mode * np.pi / 2
hi = min(np.pi / 2 - abit + mode * np.pi / 2, V / 2)
if lo > V / 2:
return 0 # mode does not exist
try:
b = brentq(_TE_mode, lo, hi, args=(V, mode))
except ValueError: # happens when both hi and lo values have same sign
return 0 # therefore no such mode exists
return b
[docs]
def TE_crossings(V):
"""
Return all the TE eigenvalues for a planar waveguide.
Args:
V : the V-parameter for the waveguide
Returns:
an array eigenvalues.
"""
ncross = int(V / np.pi) + 1
crossings = np.empty(ncross)
for mode in range(ncross):
crossings[mode] = TE_crossing(V, mode)
return crossings
[docs]
def TM_mode_plot(V, n1, n2):
"""
Plot the symmetric and asymmetric TM modes in a planar waveguide.
Args:
V: the V-parameter for the waveguide
n1: index of refraction inside the waveguide
n2: index of refraction of the cladding
Returns:
a matplotlib.pyplot object
"""
abit = 1e-5
xi = np.linspace(0, V / 2 - abit, 100)
ellipse = (n1 / n2)**2 * np.sqrt((V / 2)**2 - xi**2)
aplt = _base_mode_plot(V)
aplt.plot(xi, ellipse, ':k')
ystr = r'$\xi\,\tan\xi$ or $-\xi\,\cot\xi$ or'
ystr = ystr + r'$(n_1/n_2)^2\sqrt{V^2/4-\xi^2}$'
aplt.ylabel(ystr)
aplt.title('TM Modes in Planar Film Waveguide (V=%.2f)' % V)
ymax = (n1 / n2)**2 * V / 2
aplt.ylim(0, ymax + 1)
aplt.xlim(0, ymax + 1)
return aplt
def _TM_mode(xi, *args):
"""
Return the value of TM eigenvalue equation.
The zeros of eigenvalue equation determine the eigenvalues which
in turn are needed to determine the propagation factor for a planar
waveguide.
The planar waveguide characteristics are passed as a 4-element array.
Args:
xi : non-dimensional propagation value in waveguide
arg[0]: the V-parameter for the waveguide
arg[1]: index of the planar waveguide
arg[2]: index of the cladding
arg[3]: the desired mode (even values are symmetric)
Returns:
the distance from a solution
"""
V = args[0]
n1 = args[1]
n2 = args[2]
mode = args[3]
if mode % 2 == 0:
return xi * np.tan(xi) - (n1 / n2)**2 * np.sqrt(V**2 / 4 - xi**2)
return xi / np.tan(xi) + (n1 / n2)**2 * np.sqrt(V**2 / 4 - xi**2)
[docs]
def TM_crossing(V, n1, n2, mode):
"""
Return the TM eigenvalue for a planar waveguide mode.
The eigenvalue, xi, satisfies the eigenvalue equation for
the specified TM mode in a planar waveguide.
The solution is found numerically using the Brent method. The
only tricky bit is setting the proper range to search within.
Args:
V : the V-parameter for the waveguide
n1 : index of waveguide
n2 : index of material next to waveguide
mode : desired propagation mode
Returns:
the eigenvalue. Returns 0 if no eigenvalue.
"""
abit = 1e-5
lo = abit + mode * np.pi / 2
hi = min(np.pi / 2 - abit + mode * np.pi / 2, V / 2)
if lo > V / 2:
return 0 # mode does not exist
try:
b = brentq(_TM_mode, lo, hi, args=(V, n1, n2, mode))
except ValueError: # happens when both hi and lo values have same sign
return 0 # therefore no such mode exists
return b
[docs]
def TM_crossings(V, n1, n2):
"""
Return all the TM eigenvalues for a planar waveguide.
Args:
V : the V-parameter for the waveguide
n1 : index of waveguide
n2 : index of material next to waveguide
Returns:
an array of eigenvalues
"""
ncross = int(V / 2 / (np.pi / 2)) + 1
crossings = np.empty(ncross)
for mode in range(ncross):
crossings[mode] = TM_crossing(V, n1, n2, mode)
return crossings
def _basic_field(V, d, x, mode, xi):
"""
Calculate a field in a planar waveguide.
Args:
V : the V-parameter for the waveguide
d : thickness of the waveguide
x : desired field positions
mode : specific mode
xi : kappa*d/2 for this mode
Returns:
the field at each position x
"""
gdby2 = np.sqrt((V / 2)**2 - xi**2) # gamma*d/2
xgamma = 2 / d * gdby2 * abs(x) # gamma*x
kappa = 2 / d * xi
if mode % 2 == 0:
A = np.cos(kappa * x)
B = np.cos(xi) * np.exp(gdby2 - xgamma)
else:
A = np.sin(kappa * x)
B = np.sin(xi) * np.sign(x) * np.exp(gdby2 - xgamma)
return np.where(abs(x) < d / 2, A, B)
[docs]
def TE_field(V, d, x, mode):
"""
Calculate the TE field in a planar waveguide.
Args:
V : the V-parameter for the waveguide
d : thickness of the waveguide
x : desired field positions
mode : specific mode
Returns:
the field at each position x
"""
xi = TE_crossing(V, mode)
E_y = _basic_field(V, d, x, mode, xi)
return E_y
[docs]
def TM_field(V, n1, n2, d, x, mode):
"""
Calculate the TM field in a planar waveguide.
Args:
V : the V-parameter for the waveguide
n1 : index of waveguide
n2 : index of material next to waveguide
d : thickness of the waveguide
x : desired field positions
mode : specific mode
Returns:
the field at each position x
"""
xi = TM_crossing(V, n1, n2, mode)
H_y = _basic_field(V, d, x, mode, xi)
return H_y
[docs]
def TE_propagation_constant(V, mode):
"""
Calculate the propagation constants for TE modes in a planar waveguide.
Args:
V : the V-parameter for the waveguide
mode : specific mode
Returns:
an array of propagation constants for each value of V
"""
b = np.empty_like(V)
for i, VV in enumerate(V):
xi = TE_crossing(VV, mode)
if xi == 0:
b[i] = 0
else:
b[i] = 1 - (2 * xi / VV)**2
return b
[docs]
def TM_propagation_constant(V, n1, n2, mode):
"""
Calculate the propagation constants for TM modes in a planar waveguide.
Args:
V : the V-parameter for the waveguide
mode : specific mode
Returns:
an array of propagation constants for each value of V
"""
b = np.empty_like(V)
for i, VV in enumerate(V):
xi = TM_crossing(VV, n1, n2, mode)
if xi == 0:
b[i] = 0
else:
b[i] = 1 - (2 * xi / VV)**2
return b