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'