From 85098641a95908fcca64a6ad3c7171dabb4a075d Mon Sep 17 00:00:00 2001 From: simbilod Date: Wed, 12 Jul 2023 13:59:53 -0700 Subject: [PATCH 1/4] propagation loss model notebook --- docs/photonics/examples/propagation_loss.py | 229 ++++++++++++++++++++ docs/references.bib | 12 +- 2 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 docs/photonics/examples/propagation_loss.py diff --git a/docs/photonics/examples/propagation_loss.py b/docs/photonics/examples/propagation_loss.py new file mode 100644 index 00000000..c69c6040 --- /dev/null +++ b/docs/photonics/examples/propagation_loss.py @@ -0,0 +1,229 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: light +# format_version: '1.5' +# jupytext_version: 1.14.5 +# kernelspec: +# display_name: femwell +# language: python +# name: python3 +# --- + +# # Physics-informed propagation loss model +# +# The ability to locally refine the mesh makes FEM well-suited to problems with very different lengthscales. +# +# One such problem is empirically modeling the propagation loss due to sidewall roughness, for instance as performed in \cite{Lindecrantz2014}. + +import shapely +from collections import OrderedDict +from shapely.ops import clip_by_rect +from shapely.affinity import scale +import numpy as np +from skfem.io.meshio import from_meshio +from femwell.mesh import mesh_from_OrderedDict +from femwell.visualization import plot_domains +from skfem import Basis, ElementTriP0 +from femwell.maxwell.waveguide import compute_modes +from scipy.optimize import curve_fit + +# Assume there is some information available about TE waveguide loss as a function of wavelength and width: + +# + +# Foundry-reported information +wavelengths = (1.55, 1.55) +widths = (0.5, 1) +slab_heights = (0.0, 0.0) +losses = ydata = np.array([2, 1]) +core_thickness = 0.22 +n_si = 3.45 +n_sio2 = 1.44 + +# Model hyperparameters +sidewall_extent = 0.01 + +# Format training data +xdata = [] +for wavelength, width, slab_height in zip(wavelengths, widths, slab_heights): + xdata.append((wavelength, width, slab_height)) +xdata = np.array(xdata) + + +# - + +# Assuming sidewall roughness dominates the loss, we prepare the following mesh: + +def waveguide(core_width, + slab_thickness, + core_thickness = core_thickness, + slab_width = 4, + sidewall_extent = 0.02, + sidewall_k = 1E-4, + material_k = 1E-5 + ): + + core = shapely.geometry.box(-core_width / 2, 0, +core_width / 2, core_thickness) + + # Core sidewalls (only keep side extensions) + core_sidewalls = core.buffer(sidewall_extent, resolution=8) + core_sidewalls = clip_by_rect(core_sidewalls, -np.inf, 0, np.inf, core_thickness) + + if slab_thickness: + slab = shapely.geometry.box(-slab_width / 2, 0, +slab_width / 2, slab_thickness) + waveguide = shapely.union(core, slab) + clad = scale(waveguide.buffer(5, resolution=8), xfact=0.5) + polygons = OrderedDict( + slab=slab, + core=core, + core_sidewalls=core_sidewalls, + clad=clad, + ) + else: + clad = scale(core.buffer(5, resolution=8), xfact=0.5) + polygons = OrderedDict( + core=core, + core_sidewalls=core_sidewalls, + clad=clad, + ) + resolutions = dict(core={"resolution": 0.03, "distance": 0.5}, + core_sidewalls={"resolution": 0.005, "distance": 0.5}, + slab={"resolution": 0.06, "distance": 0.5}, + ) + + mesh = from_meshio( + mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=10) + ) + + basis0 = Basis(mesh, ElementTriP0()) + epsilon = basis0.zeros(dtype=complex) + + materials = {"core": n_si - 1j * material_k, + "core_sidewalls": n_sio2 - 1j * sidewall_k, + "clad": n_sio2} + + if slab_thickness: + materials["slab"] = n_si - 1j * material_k + + for subdomain, n in materials.items(): + epsilon[basis0.get_dofs(elements=subdomain)] = n**2 + + return mesh, basis0, epsilon + + +# + +mesh, basis0, epsilon = waveguide(core_width = 0.5, + slab_thickness = 0.0, + core_thickness = 0.22, + ) + +plot_domains(mesh) +basis0.plot(epsilon.real, colorbar=True).show() +basis0.plot(epsilon.imag, colorbar=True).show() + + +# - + +# Now that we have a simulation, we can compute TE0 modes, and fit the hyperparameters `sidewall_extent` and `sidewall_index` to get a better model for loss as a function of waveguide geometry: + +def compute_propagation_loss(wavelength, + core_width, + slab_thickness, + core_thickness = core_thickness, + slab_width = 4, + sidewall_extent = sidewall_extent, + sidewall_k = 1E-4, + material_k = 1E-5, + ): + + mesh, basis0, epsilon = waveguide(core_width = core_width, + slab_thickness = slab_thickness, + core_thickness = core_thickness, + slab_width = slab_width, + sidewall_extent = sidewall_extent, + sidewall_k = sidewall_k, + material_k = material_k + ) + + modes = compute_modes(basis0, epsilon, wavelength=wavelength, num_modes=1, order=2) + + keff = modes[0].n_eff.imag + wavelength_m = wavelength * 1e-6 # convert to m + alpha = -4 * np.pi * keff / wavelength_m + return 10 * np.log10(np.exp(1)) * alpha * 1e-2 # convert to cm + + +for wavelength, core_width, slab_thickness, loss in zip(wavelengths, widths, slab_heights, losses): + + predicted_loss = compute_propagation_loss(wavelength=wavelength, + core_width=core_width, + slab_thickness=slab_thickness, + core_thickness = core_thickness, + slab_width = 4, + sidewall_extent = sidewall_extent, + sidewall_k = 3E-4, + material_k = 2.5E-6, + ) + + print(wavelength, core_width, slab_thickness, predicted_loss, loss) + + +# Pretty close, refine through optimization: + +def objective_vector(xdata, sidewall_k, material_k): + losses_obj = [] + for wavelength, width, slab_height in xdata: + losses_obj.append(compute_propagation_loss(wavelength=wavelength, + core_width=width, + slab_thickness=slab_height, + core_thickness = core_thickness, + slab_width = 4, + sidewall_extent = sidewall_extent, + sidewall_k = sidewall_k, + material_k = material_k, + )) + return losses_obj + + +popt, pcov = curve_fit(objective_vector, xdata, ydata, bounds=(0, [1E-2, 1E-2]), p0 = (3E-4, 1E-6)) + +popt, pcov + +for wavelength, core_width, slab_thickness, loss in zip(wavelengths, widths, slab_heights, losses): + + predicted_loss = compute_propagation_loss(wavelength=wavelength, + core_width=core_width, + slab_thickness=slab_thickness, + core_thickness = core_thickness, + slab_width = 4, + sidewall_extent = sidewall_extent, + sidewall_k = popt[0], + material_k = popt[1], + ) + + print(wavelength, core_width, slab_thickness, predicted_loss, loss) + +widths_plot = np.linspace(0.275, 2.0, 19) +losses_plot_strip = [] +for width in widths_plot: + losses_plot_strip.append(compute_propagation_loss(wavelength=1.55, + core_width=width, + slab_thickness=0.0, + core_thickness = core_thickness, + slab_width = 4, + sidewall_extent = sidewall_extent, + sidewall_k = popt[0], + material_k = popt[1], + )) + +# + +import matplotlib.pyplot as plt + +plt.plot(widths_plot, losses_plot_strip, label="strip model") +plt.scatter(widths, losses, label="strip data") + +plt.legend() +plt.xlabel("Core width (um)") +plt.ylabel("Propagation loss (dB/cm)") diff --git a/docs/references.bib b/docs/references.bib index c2ffe6ff..9c6e7fdd 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -247,4 +247,14 @@ @BOOK{Jin2015 ISBN = {978-1-118-84202-7}, PUBLISHER = {John Wiley & Sons}, ADDRESS = {New York}, -} \ No newline at end of file +} + +@ARTICLE{Lindecrantz2014, + author={Lindecrantz, Susan M. and Hellesø, Olav Gaute}, + journal={IEEE Photonics Technology Letters}, + title={Estimation of Propagation Losses for Narrow Strip and Rib Waveguides}, + year={2014}, + volume={26}, + number={18}, + pages={1836-1839}, + doi={10.1109/LPT.2014.2337055}} From b992ba2004690416c1309863a1910c0c91024561 Mon Sep 17 00:00:00 2001 From: simbilod Date: Wed, 12 Jul 2023 14:05:06 -0700 Subject: [PATCH 2/4] formatting and reference --- docs/photonics/examples/propagation_loss.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/photonics/examples/propagation_loss.py b/docs/photonics/examples/propagation_loss.py index c69c6040..2f6921b4 100644 --- a/docs/photonics/examples/propagation_loss.py +++ b/docs/photonics/examples/propagation_loss.py @@ -14,9 +14,11 @@ # # Physics-informed propagation loss model # -# The ability to locally refine the mesh makes FEM well-suited to problems with very different lengthscales. +# The ability to locally refine the mesh makes FEM well-suited to problems with very different lengthscales. # -# One such problem is empirically modeling the propagation loss due to sidewall roughness, for instance as performed in \cite{Lindecrantz2014}. +# One such problem is empirically modeling the propagation loss due to sidewall roughness, for instance as performed in {cite}`Lindecrantz2014`. + +# + tags=["remove-stderr"] import shapely from collections import OrderedDict @@ -29,6 +31,7 @@ from skfem import Basis, ElementTriP0 from femwell.maxwell.waveguide import compute_modes from scipy.optimize import curve_fit +# - # Assume there is some information available about TE waveguide loss as a function of wavelength and width: @@ -227,3 +230,11 @@ def objective_vector(xdata, sidewall_k, material_k): plt.legend() plt.xlabel("Core width (um)") plt.ylabel("Propagation loss (dB/cm)") +# - + +# ## Bibliography +# +# ```{bibliography} +# :style: unsrt +# :filter: docname in docnames +# ``` From 46b512599e096056c4bf9a40bd1468ca37ee4d53 Mon Sep 17 00:00:00 2001 From: simbilod Date: Wed, 12 Jul 2023 14:06:33 -0700 Subject: [PATCH 3/4] pre-commit --- docs/photonics/examples/propagation_loss.py | 195 +++++++++++--------- 1 file changed, 105 insertions(+), 90 deletions(-) diff --git a/docs/photonics/examples/propagation_loss.py b/docs/photonics/examples/propagation_loss.py index 2f6921b4..9edf77e4 100644 --- a/docs/photonics/examples/propagation_loss.py +++ b/docs/photonics/examples/propagation_loss.py @@ -20,17 +20,20 @@ # + tags=["remove-stderr"] -import shapely from collections import OrderedDict -from shapely.ops import clip_by_rect -from shapely.affinity import scale + import numpy as np +import shapely +from scipy.optimize import curve_fit +from shapely.affinity import scale +from shapely.ops import clip_by_rect +from skfem import Basis, ElementTriP0 from skfem.io.meshio import from_meshio + +from femwell.maxwell.waveguide import compute_modes from femwell.mesh import mesh_from_OrderedDict from femwell.visualization import plot_domains -from skfem import Basis, ElementTriP0 -from femwell.maxwell.waveguide import compute_modes -from scipy.optimize import curve_fit + # - # Assume there is some information available about TE waveguide loss as a function of wavelength and width: @@ -59,15 +62,16 @@ # Assuming sidewall roughness dominates the loss, we prepare the following mesh: -def waveguide(core_width, - slab_thickness, - core_thickness = core_thickness, - slab_width = 4, - sidewall_extent = 0.02, - sidewall_k = 1E-4, - material_k = 1E-5 - ): - + +def waveguide( + core_width, + slab_thickness, + core_thickness=core_thickness, + slab_width=4, + sidewall_extent=0.02, + sidewall_k=1e-4, + material_k=1e-5, +): core = shapely.geometry.box(-core_width / 2, 0, +core_width / 2, core_thickness) # Core sidewalls (only keep side extensions) @@ -91,22 +95,23 @@ def waveguide(core_width, core_sidewalls=core_sidewalls, clad=clad, ) - resolutions = dict(core={"resolution": 0.03, "distance": 0.5}, - core_sidewalls={"resolution": 0.005, "distance": 0.5}, - slab={"resolution": 0.06, "distance": 0.5}, - ) - - mesh = from_meshio( - mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=10) - ) - + resolutions = dict( + core={"resolution": 0.03, "distance": 0.5}, + core_sidewalls={"resolution": 0.005, "distance": 0.5}, + slab={"resolution": 0.06, "distance": 0.5}, + ) + + mesh = from_meshio(mesh_from_OrderedDict(polygons, resolutions, default_resolution_max=10)) + basis0 = Basis(mesh, ElementTriP0()) epsilon = basis0.zeros(dtype=complex) - materials = {"core": n_si - 1j * material_k, - "core_sidewalls": n_sio2 - 1j * sidewall_k, - "clad": n_sio2} - + materials = { + "core": n_si - 1j * material_k, + "core_sidewalls": n_sio2 - 1j * sidewall_k, + "clad": n_sio2, + } + if slab_thickness: materials["slab"] = n_si - 1j * material_k @@ -117,10 +122,11 @@ def waveguide(core_width, # + -mesh, basis0, epsilon = waveguide(core_width = 0.5, - slab_thickness = 0.0, - core_thickness = 0.22, - ) +mesh, basis0, epsilon = waveguide( + core_width=0.5, + slab_thickness=0.0, + core_thickness=0.22, +) plot_domains(mesh) basis0.plot(epsilon.real, colorbar=True).show() @@ -131,95 +137,104 @@ def waveguide(core_width, # Now that we have a simulation, we can compute TE0 modes, and fit the hyperparameters `sidewall_extent` and `sidewall_index` to get a better model for loss as a function of waveguide geometry: -def compute_propagation_loss(wavelength, - core_width, - slab_thickness, - core_thickness = core_thickness, - slab_width = 4, - sidewall_extent = sidewall_extent, - sidewall_k = 1E-4, - material_k = 1E-5, - ): - - mesh, basis0, epsilon = waveguide(core_width = core_width, - slab_thickness = slab_thickness, - core_thickness = core_thickness, - slab_width = slab_width, - sidewall_extent = sidewall_extent, - sidewall_k = sidewall_k, - material_k = material_k - ) + +def compute_propagation_loss( + wavelength, + core_width, + slab_thickness, + core_thickness=core_thickness, + slab_width=4, + sidewall_extent=sidewall_extent, + sidewall_k=1e-4, + material_k=1e-5, +): + mesh, basis0, epsilon = waveguide( + core_width=core_width, + slab_thickness=slab_thickness, + core_thickness=core_thickness, + slab_width=slab_width, + sidewall_extent=sidewall_extent, + sidewall_k=sidewall_k, + material_k=material_k, + ) modes = compute_modes(basis0, epsilon, wavelength=wavelength, num_modes=1, order=2) keff = modes[0].n_eff.imag wavelength_m = wavelength * 1e-6 # convert to m alpha = -4 * np.pi * keff / wavelength_m - return 10 * np.log10(np.exp(1)) * alpha * 1e-2 # convert to cm + return 10 * np.log10(np.exp(1)) * alpha * 1e-2 # convert to cm for wavelength, core_width, slab_thickness, loss in zip(wavelengths, widths, slab_heights, losses): - - predicted_loss = compute_propagation_loss(wavelength=wavelength, - core_width=core_width, - slab_thickness=slab_thickness, - core_thickness = core_thickness, - slab_width = 4, - sidewall_extent = sidewall_extent, - sidewall_k = 3E-4, - material_k = 2.5E-6, - ) + predicted_loss = compute_propagation_loss( + wavelength=wavelength, + core_width=core_width, + slab_thickness=slab_thickness, + core_thickness=core_thickness, + slab_width=4, + sidewall_extent=sidewall_extent, + sidewall_k=3e-4, + material_k=2.5e-6, + ) print(wavelength, core_width, slab_thickness, predicted_loss, loss) # Pretty close, refine through optimization: + def objective_vector(xdata, sidewall_k, material_k): losses_obj = [] for wavelength, width, slab_height in xdata: - losses_obj.append(compute_propagation_loss(wavelength=wavelength, - core_width=width, - slab_thickness=slab_height, - core_thickness = core_thickness, - slab_width = 4, - sidewall_extent = sidewall_extent, - sidewall_k = sidewall_k, - material_k = material_k, - )) + losses_obj.append( + compute_propagation_loss( + wavelength=wavelength, + core_width=width, + slab_thickness=slab_height, + core_thickness=core_thickness, + slab_width=4, + sidewall_extent=sidewall_extent, + sidewall_k=sidewall_k, + material_k=material_k, + ) + ) return losses_obj -popt, pcov = curve_fit(objective_vector, xdata, ydata, bounds=(0, [1E-2, 1E-2]), p0 = (3E-4, 1E-6)) +popt, pcov = curve_fit(objective_vector, xdata, ydata, bounds=(0, [1e-2, 1e-2]), p0=(3e-4, 1e-6)) popt, pcov for wavelength, core_width, slab_thickness, loss in zip(wavelengths, widths, slab_heights, losses): - - predicted_loss = compute_propagation_loss(wavelength=wavelength, - core_width=core_width, - slab_thickness=slab_thickness, - core_thickness = core_thickness, - slab_width = 4, - sidewall_extent = sidewall_extent, - sidewall_k = popt[0], - material_k = popt[1], - ) + predicted_loss = compute_propagation_loss( + wavelength=wavelength, + core_width=core_width, + slab_thickness=slab_thickness, + core_thickness=core_thickness, + slab_width=4, + sidewall_extent=sidewall_extent, + sidewall_k=popt[0], + material_k=popt[1], + ) print(wavelength, core_width, slab_thickness, predicted_loss, loss) widths_plot = np.linspace(0.275, 2.0, 19) losses_plot_strip = [] for width in widths_plot: - losses_plot_strip.append(compute_propagation_loss(wavelength=1.55, - core_width=width, - slab_thickness=0.0, - core_thickness = core_thickness, - slab_width = 4, - sidewall_extent = sidewall_extent, - sidewall_k = popt[0], - material_k = popt[1], - )) + losses_plot_strip.append( + compute_propagation_loss( + wavelength=1.55, + core_width=width, + slab_thickness=0.0, + core_thickness=core_thickness, + slab_width=4, + sidewall_extent=sidewall_extent, + sidewall_k=popt[0], + material_k=popt[1], + ) + ) # + import matplotlib.pyplot as plt From 254dd46fe629290b069a9447b5c114b27ee25f7d Mon Sep 17 00:00:00 2001 From: simbilod Date: Wed, 12 Jul 2023 14:14:48 -0700 Subject: [PATCH 4/4] add to toc --- docs/_toc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_toc.yml b/docs/_toc.yml index d9291298..96eb032c 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -36,6 +36,7 @@ parts: - file: photonics/examples/coupled_mode_theory.py - file: photonics/examples/depletion_waveguide.py - file: photonics/examples/refinement.py + - file: photonics/examples/propagation_loss.py - caption: Benchmarks chapters: