diff --git a/Makefile b/Makefile index 58726463..212c2a4b 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ venv: install: uv venv --python 3.11 - uv pip install -e .[dev,docs,femwell,gmsh,meow,sax,tidy3d,klayout,vlsir] + uv pip install -e .[dev,docs,femwell,gmsh,meow,sax,tidy3d,klayout,vlsir,emode] uv run pre-commit install dev: test-data gmsh elmer install diff --git a/README.md b/README.md index 3dcb72cc..6dd07698 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,12 @@ Run simulations with GDSFactory by installing plugins. - `palace` for full-wave driven (S parameter) and electrostatic (capacitive) simulations. - EME - `meow` Eigen Mode Expansion (EME). + - `EMode` - Mode Solver - Tidy3d - Femwell - MPB + - `EMode` - TCAD - `devsim` TCAD device simulator. - Circuit simulations @@ -46,7 +48,7 @@ pip install "gdsfactory[full]" --upgrade Or list the plugins individually: ```bash -pip install "gplugins[devsim,femwell,gmsh,schematic,meow,meshwell,sax,tidy3d]" --upgrade +pip install "gplugins[devsim,femwell,gmsh,schematic,meow,meshwell,ray,sax,tidy3d,emode]" --upgrade ``` Or install only the plugins you need. For example: diff --git a/docs/_toc.yml b/docs/_toc.yml index f6845f22..8aed0d03 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -32,6 +32,7 @@ parts: - file: notebooks/mode_solver_fdfd - file: notebooks/mpb_001_mpb_waveguide - file: notebooks/meow_01 + - file: notebooks/emode - file: plugins_fdtd sections: - file: notebooks/tidy3d_00_tidy3d diff --git a/docs/plugins_mode_solver.md b/docs/plugins_mode_solver.md index 25d91e03..3232b026 100644 --- a/docs/plugins_mode_solver.md +++ b/docs/plugins_mode_solver.md @@ -2,14 +2,18 @@ A mode solver computes the modes supported by a waveguide cross-section at a particular wavelength. Modes are definite-frequency eigenstates of Maxwell's equations. -You can use 3 open source mode solvers: +You can use several mode solvers with GDSFactory: -1. tidy3d. Finite difference Frequency Domain (FDFD). -2. MPB. FDFD with periodic boundary conditions. -3. Femwell. Finite Element (FEM). +**Open Source:** +- ``tidy3d``: Finite difference Frequency Domain (FDFD). +- ``MPB``: FDFD with periodic boundary conditions. +- ``Femwell``: Finite Element (FEM). +- ``meow``: The tidy3d mode solver is also used by the MEOW plugin to get the Sparameters of components via Eigenmode Expansion. -The tidy3d mode solver is also used by the MEOW plugin to get the Sparameters of components via Eigenmode Expansion. -Notice that the tidy3d FDTD solver is not open source as it runs on the cloud server, but the mode solver is open source and runs locally on your computer. + **Notice**: The tidy3d FDTD solver is not open source as it runs on the cloud server, but the mode solver is open source and runs locally on your computer. + +**Proprietary:** +- ``EMode``: A versatile waveguide mode solver using the Finite-Difference Frequency Domain (FDFD) method, and a propagation tool using the Eigenmode Expansion method (EME). Requires the EMode software suite to be installed separately. See [docs.emodephotonix.com/installation](https://docs.emodephotonix.com/installation) for installation instructions. ```{tableofcontents} ``` diff --git a/gplugins/emode/__init__.py b/gplugins/emode/__init__.py new file mode 100644 index 00000000..88d3fc07 --- /dev/null +++ b/gplugins/emode/__init__.py @@ -0,0 +1,4 @@ +from emodeconnection import open_file, get, inspect +from .utils import EMode + +__all__ = ["EMode", "open_file", "get", "inspect"] diff --git a/gplugins/emode/tests/SOI.py b/gplugins/emode/tests/SOI.py new file mode 100644 index 00000000..b610544c --- /dev/null +++ b/gplugins/emode/tests/SOI.py @@ -0,0 +1,60 @@ + +import gplugins.emode as emc +from gdsfactory.cross_section import rib +from gdsfactory.generic_tech import LAYER_STACK +from gdsfactory.technology import LayerStack + + +## Set up GDSFactory LayerStack and CrossSection in units of microns +layer_stack = LayerStack( + layers={ + k: LAYER_STACK.layers[k] + for k in ( + "core", + "clad", + "slab90", + "box", + ) + } +) + +layer_stack.layers["core"].thickness = 0.22 +layer_stack.layers["core"].zmin = 0 + +layer_stack.layers["slab90"].thickness = 0.09 +layer_stack.layers["slab90"].zmin = 0 + +layer_stack.layers["box"].thickness = 1.5 +layer_stack.layers["box"].zmin = -1.5 + +layer_stack.layers["clad"].thickness = 1.5 +layer_stack.layers["clad"].zmin = 0 + +## Connect and initialize EMode +em = emc.EMode() + +## build_waveguide converts units to nanometers, which EMode's default +modes = em.build_waveguide( + cross_section=rib(width=0.6), + layer_stack=layer_stack, + wavelength=1.55, + num_modes=1, + x_resolution=0.010, + y_resolution=0.010, + window_width = 3.0, + window_height = 3.0, + background_refractive_index='Air', + max_effective_index=2.631, +) + +## Launch FDM solver +em.FDM() + +## Display the effective indices, TE fractions, and core confinement +em.report() + +## Plot the field and refractive index profiles +em.plot() + +## Close EMode +em.close() diff --git a/gplugins/emode/utils.py b/gplugins/emode/utils.py new file mode 100644 index 00000000..42233ea0 --- /dev/null +++ b/gplugins/emode/utils.py @@ -0,0 +1,72 @@ +import emodeconnection as emc +from gdsfactory import CrossSection +from gdsfactory.technology import LayerStack + +class EMode(emc.EMode): + def build_waveguide( + self, + cross_section: CrossSection, + layer_stack: LayerStack, + **kwargs, + ) -> None: + """Builds a waveguide structure in EMode based on GDSFactory CrossSection and LayerStack. + + Args: + cross_section: A GDSFactory CrossSection object defining the waveguide's geometry. + layer_stack: A GDSFactory LayerStack object defining the material layers. + **kwargs: Additional keyword arguments to be passed directly to EMode's settings() function. + """ + nm = 1e3 # convert microns to nanometers + dim_keys = [ + 'wavelength', + 'x_resolution', + 'y_resolution', + 'window_width', + 'window_height', + 'bend_radius', + 'expansion_resolution', + 'expansion_size', + 'propagation_resolution', + ] + + # Pass all kwargs directly to EMode's settings function + kwargs = {key: (value * nm if key in dim_keys else value) for key, value in kwargs.items()} + self.settings(**kwargs) + + # Get a list of EMode materials for cleaning GDSFactory material names + emode_materials = self.get('materials') + + # Process layer_stack to create EMode shapes + max_order = max([info.mesh_order for name, info in layer_stack.layers.items()]) + min_zmin = min([info.zmin for name, info in layer_stack.layers.items()]) + + for layer_name, layer_info in layer_stack.layers.items(): + + material = next( + (mat for mat in emode_materials if mat.lower() == layer_info.material.lower()), + layer_info.material + ) + + shape_info = { + 'name': layer_name, + 'refractive_index': material, + 'height': layer_info.thickness * nm, + 'mask': layer_info.width_to_z * nm, + 'sidewall_angle': layer_info.sidewall_angle, + 'etch_depth': layer_info.thickness * nm if layer_info.width_to_z > 0 else 0, + 'position': [0.0, (layer_info.zmin - min_zmin + layer_info.thickness/2) * nm], + 'priority': max_order - layer_info.mesh_order + 1, + } + + # Find a matching CrossSection for this layer + xsection_ind = [k for k, s in enumerate(cross_section.sections) if str(layer_info.layer) == str(s.layer) or str(layer_info.derived_layer) == str(s.layer)] + + # Apply settings from the matching CrossSection + if len(xsection_ind) > 0: + xsection = cross_section.sections[xsection_ind[0]] + shape_info['mask'] = xsection.width * nm + shape_info['mask_offset'] = xsection.offset * nm + + self.shape(**shape_info) + + return diff --git a/notebooks/emode.ipynb b/notebooks/emode.ipynb new file mode 100644 index 00000000..e2fd3114 --- /dev/null +++ b/notebooks/emode.ipynb @@ -0,0 +1,88 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dec55ff8", + "metadata": {}, + "source": [ + "# EMode - Photonic Device Simulation\n", + "\n", + "The EMode plugin integrates simulation capabilities from [**EMode Photonix**](https://emodephotonix.com) with GDSFactory. This plugin enables the analysis of photonic devices through:\n", + "\n", + "- Waveguide mode solving using a Finite-Difference Frequency Domain (FDFD) method.\n", + "- Propagation simulations using the Eigenmode Expansion (EME) method.\n", + "\n", + "Cross-sectional simulations are available with a free EMode2D license. The propagation module requires an EMode3D license.\n", + "\n", + "See the complete EMode documentation at [**EMode Docs**](https://docs.emodephotonix.com).\n", + "\n", + "Get an EMode license at the [**EMode Shop**](https://emodephotonix.com/get-emode).\n", + "\n", + "## Software Requirement\n", + "\n", + "This plugin requires that the EMode software is already installed on the user's computer. Please refer to the installation guide at [docs.emodephotonix.com/installation](https://docs.emodephotonix.com/installation) for detailed instructions.\n", + "\n", + "## Using EMode Examples with gplugins\n", + "\n", + "All of the [**EMode examples**](https://docs.emodephotonix.com/examples) can be adapted for use with the GDSFactory plugin. Simply replace the standard EMode import:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "f0fbf103", + "metadata": {}, + "outputs": [], + "source": [ + "import emodeconnection as emc" + ] + }, + { + "cell_type": "markdown", + "id": "6f832fad", + "metadata": {}, + "source": [ + "with the plugin-specific import:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b62c1bf", + "metadata": {}, + "outputs": [], + "source": [ + "import gplugins.emode as emc" + ] + }, + { + "cell_type": "markdown", + "id": "1f863dfd", + "metadata": {}, + "source": [ + "The ``gplugins.emode`` import provides the same interface and usage as the ``emodeconnection`` package, detailed in the [EMode Python connection documentation](https://docs.emodephotonix.com/emodeconnection_python)." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index b8f4e1e7..8e1c982a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ sax = ["sax~=0.15.14"] schematic = ["bokeh", "ipywidgets", "natsort"] tidy3d = ["tidy3d>=2.8.2,<2.9", "gdstk"] vlsir = ["vlsir", "vlsirtools"] +emode = ["emodeconnection"] [tool.codespell] ignore-words-list = 'te, te/tm, te, ba, fpr, fpr_spacing, ro, nd, donot, schem, Ue'