Skip to content

Add EMode plugin#733

Open
estanton22 wants to merge 5 commits into
gdsfactory:mainfrom
estanton22:add-emode-plugin
Open

Add EMode plugin#733
estanton22 wants to merge 5 commits into
gdsfactory:mainfrom
estanton22:add-emode-plugin

Conversation

@estanton22

@estanton22 estanton22 commented Jul 9, 2026

Copy link
Copy Markdown

What is EMode?

EMode (EMode Photonix) is a commercial photonic simulation package with an electromagnetic waveguide mode solver based on the Finite Difference Method (FDM), eigenmode expansion propagation (EME), nonlinear photonics, and thermal/electrical FEM solvers. Full API documentation: https://docs.emodephotonix.com

What this PR adds

A new gplugins/emode subpackage that bridges gdsfactory and EMode:

  • Pure translation functions: get_shapes_from_layer_stack converts a gdsfactory LayerStack + CrossSection into EMode shape definitions (mask widths/offsets from cross-section sections, mesh order mapped to EMode shape priority, positions referenced to the bottom of the stack, material names matched case-insensitively against the EMode material database), and get_emode_settings converts gdsfactory's um units to EMode's nm units.
  • EMode session class: subclasses emodeconnection.EMode and adds build_waveguide(cross_section, layer_stack, **settings). Any EMode function is callable as a method on the session (FDM(), EME(), report(), plot(), ...).
  • emode extra: depends only on the pip-installable, MIT-friendly emodeconnection client (pure Python: numpy/scipy/pydantic). The EMode application itself is a separate desktop installation with its own license (a free trial is available) and is not bundled or required to install the plugin.
  • Docs: EMode listed on the mode-solver page, API reference entries, and a new notebooks/emode_01.ipynb SOI rib waveguide example.

Testing

Runs in CI (added to the test_code.yml matrix; only needs pip install .[emode,dev], no EMode installation or license):

  • gplugins/emode/tests/test_emode.py covers unit conversion, layer-stack-to-shape translation, cross-section width/offset matching, mesh-order-to-priority mapping, material name matching, error handling, and build_waveguide against a stub session that records calls instead of launching the application. Collection is guarded with pytest.importorskip("emodeconnection").

Requires a full EMode installation + license (not run in CI):

  • notebooks/emode_01.ipynb and the __main__ block in gplugins/emode/emode.py. The notebook is in docs/_config.yml execute_notebooks exclude patterns (same approach as the Lumerical notebook), so docs builds do not execute it.

The full flow (build_waveguide → FDM → report) was verified locally against a real EMode installation: the SOI rib example yields n_eff = 2.608 for the fundamental TE mode.

🤖 Generated with Claude Code

Summary by Sourcery

Add a new emode plugin integrating the commercial EMode Photonix mode solver with gdsfactory cross-sections and layer stacks, and wire it into docs, tests, CI, and build extras.

New Features:

  • Introduce gplugins.emode subpackage with an EMode session class and helpers to convert gdsfactory geometry and settings into EMode shapes and units.
  • Add an example Jupyter notebook demonstrating an SOI rib waveguide workflow using the emode plugin.

Enhancements:

  • Register the emode extra dependency on the emodeconnection client and include it in local install, coverage, and docs build targets.
  • Update mode-solver documentation and README to mention the EMode plugin and link to external EMode resources.

Build:

  • Add an emode extra to pyproject.toml and include it in Makefile install, test, and coverage commands.

CI:

  • Extend test_code and pages workflows to install and run the emode plugin via the emode extra.
  • Enable pre-commit Ruff checks for the new emode plugin directory.

Documentation:

  • Document the EMode plugin on the mode-solver page and add an emode_01 example notebook to the docs TOC while excluding it from automatic execution during builds.

Tests:

  • Add CI-safe tests for the emode plugin covering unit conversion, geometry translation, material matching, and build_waveguide behavior via a stub EMode session.
  • Activate the generic gdsfactory PDK in emode test configuration and include the emode plugin in the test_code workflow matrix and Makefile test loops.

Eric Stanton and others added 2 commits July 9, 2026 10:08
Adds gplugins/emode, a bridge to EMode Photonix (commercial FDM
waveguide mode solver with EME propagation, nonlinear photonics, and
thermal/electrical FEM solvers). The plugin translates gdsfactory
cross-sections and layer stacks into EMode geometry via the
pip-installable emodeconnection client; running simulations requires a
local EMode installation and license.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Add a new gplugins.emode plugin that translates gdsfactory cross-sections and layer stacks into EMode Photonix geometry, exposes an EMode session class with a build_waveguide helper, and wires the plugin into tests, docs, extras, CI, and tooling.

Sequence diagram for EMode.build_waveguide integration

sequenceDiagram
    actor User
    participant EMode as EModeSession
    participant emc as emodeconnection_EMode

    User->>EMode: build_waveguide(cross_section, layer_stack, settings)
    EMode->>EMode: get_emode_settings(settings)
    EMode->>emc: settings(**converted_settings)
    EMode->>emc: get("materials")
    emc-->>EMode: materials
    EMode->>EMode: get_shapes_from_layer_stack(cross_section, layer_stack, materials)
    loop for each shape
        EMode->>emc: shape(**shape)
    end
Loading

File-Level Changes

Change Details Files
Introduce the EMode plugin subpackage with translation helpers and an EMode session class.
  • Implement get_emode_settings to convert dimensional settings from microns to nanometers based on a fixed key whitelist.
  • Implement get_emode_material for case-insensitive matching of gdsfactory material names against EMode’s material database with graceful fallback.
  • Implement get_shapes_from_layer_stack to map LayerStack and CrossSection data into EMode shape dictionaries, including mask/offset, etch depth, vertical positioning from stack bottom, and mesh_order-to-priority mapping.
  • Define the EMode session class subclassing emodeconnection.EMode and add build_waveguide to apply settings conversion, fetch materials, and create shapes via EMode’s API.
  • Provide a main example that builds and solves an SOI rib waveguide using the plugin against a real EMode installation.
gplugins/emode/emode.py
gplugins/emode/__init__.py
Add CI-safe tests and test scaffolding for the EMode plugin’s translation logic and build_waveguide helper.
  • Create a LayerStack fixture representing a pinned SOI rib geometry with specific thicknesses, zmin values, and mesh_order settings.
  • Add unit tests for get_emode_settings including conversion of dimensional fields and passthrough of non-dimensional and None values.
  • Add tests for get_emode_material to verify case-insensitive matching and fallback for unknown materials.
  • Add tests for get_shapes_from_layer_stack validating layer ordering, patterned vs blanket layers, mask/offset mapping, vertical positions, priority computation, material matching, and error conditions such as empty stacks or missing materials.
  • Implement a fake EMode session to record settings and shapes and test build_waveguide’s interaction with the session without launching EMode.
  • Ensure gpdk PDK activation via a test conftest module for consistent layer definitions.
gplugins/emode/tests/test_emode.py
gplugins/emode/tests/conftest.py
Wire the EMode plugin into project tooling, optional extras, CI workflows, coverage, and documentation.
  • Extend pre-commit ruff and ruff-format file filters to include the emode plugin directory.
  • Update Makefile install, uv-test loop, and cov target to include the emode extra and emode tests/coverage.
  • Add an emode extra in pyproject.toml that depends on emodeconnection and include it in docs and Pages uv sync commands.
  • Update test_code CI matrix to run tests for the emode plugin and adjust Pages build workflow to install the emode extra.
  • Document EMode as an additional mode-solver option in plugins_mode_solver.md and README.md, including licensing notes and a link to EMode docs.
  • Register the emode_01 notebook in the docs ToC while excluding it from execution via docs/_config.yml execute.excluded, mirroring other licensed solvers.
  • Add a new example notebook, notebooks/emode_01.ipynb, demonstrating an SOI rib workflow with the plugin and explaining requirements.
  • Record the change in the changelog fragment file.
.pre-commit-config.yaml
Makefile
.github/workflows/pages.yml
.github/workflows/test_code.yml
pyproject.toml
docs/plugins_mode_solver.md
docs/_toc.yml
docs/_config.yml
README.md
notebooks/emode_01.ipynb
.changelog.d/733.added

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the emode plugin to interface gdsfactory with EMode Photonix, enabling commercial waveguide mode solving and EME propagation. It includes the core translation module, unit tests using a stub session, documentation, and an introductory notebook. The reviewer identified several critical robustness and correctness issues in gplugins/emode/emode.py, including potential TypeError and AttributeError crashes when handling None values for optional settings, materials, or layer properties. Additionally, the reviewer pointed out incorrect geometry translation logic regarding fallback mask widths and etching depths for vertical sidewalls, providing actionable refactoring suggestions to resolve these issues.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread gplugins/emode/emode.py
Comment thread gplugins/emode/emode.py
Comment thread gplugins/emode/emode.py
Comment thread gplugins/emode/emode.py
Address review feedback: a layer matching a cross-section section is
patterned (mask/mask_offset from the section, etched through its full
thickness); layers without a match are blanket layers. width_to_z is a
normalized z-reference in gdsfactory, not a width, so it no longer
feeds the mask. Also pass None settings through unchanged and raise a
clear error for layers without a material.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@estanton22 estanton22 marked this pull request as ready for review July 9, 2026 18:22

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The SOI rib example geometry and workflow are duplicated between the __main__ block in gplugins/emode/emode.py and notebooks/emode_01.ipynb; consider factoring the common setup into a small helper so the example stays consistent in both places.
  • In get_shapes_from_layer_stack, the shape dictionaries are built as untyped dict[str, Any]; if the shape schema is stable, introducing a lightweight TypedDict or dataclass for shapes would make the mapping to EMode’s shape() arguments clearer and easier to maintain.
  • The _fake_session in test_emode.py uses anonymous lambdas attached to an EMode instance for recording calls; replacing this with a simple stub class with explicit settings, shape, and get methods would improve readability and make future extension of the test helper less error-prone.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The SOI rib example geometry and workflow are duplicated between the `__main__` block in `gplugins/emode/emode.py` and `notebooks/emode_01.ipynb`; consider factoring the common setup into a small helper so the example stays consistent in both places.
- In `get_shapes_from_layer_stack`, the shape dictionaries are built as untyped `dict[str, Any]`; if the shape schema is stable, introducing a lightweight `TypedDict` or dataclass for shapes would make the mapping to EMode’s `shape()` arguments clearer and easier to maintain.
- The `_fake_session` in `test_emode.py` uses anonymous lambdas attached to an `EMode` instance for recording calls; replacing this with a simple stub class with explicit `settings`, `shape`, and `get` methods would improve readability and make future extension of the test helper less error-prone.

## Individual Comments

### Comment 1
<location path="gplugins/emode/tests/test_emode.py" line_range="57-62" />
<code_context>
+    return em
+
+
+def test_get_emode_settings_converts_dimensional_values() -> None:
+    settings = get_emode_settings(
+        wavelength=1.55,
+        window_width=3.0,
+        num_modes=2,
+        background_material="Air",
+    )
+    assert settings["wavelength"] == pytest.approx(1550.0)
</code_context>
<issue_to_address>
**suggestion (testing):** Consider parametrizing this test over all DIMENSIONAL_SETTINGS keys to lock in unit conversion coverage

This test only covers `wavelength` and `window_width`. Since `DIMENSIONAL_SETTINGS` includes several other dimensional parameters (`x_resolution`, `y_resolution`, `bend_radius`, `expansion_resolution`, `expansion_size`, `propagation_resolution`, etc.), parametrizing the test over all keys and asserting each is converted from µm to nm would improve coverage and make the suite resilient to future additions or changes to `DIMENSIONAL_SETTINGS`.

Suggested implementation:

```python
@pytest.mark.parametrize("key", DIMENSIONAL_SETTINGS.keys())
def test_get_emode_settings_converts_dimensional_values(key: str) -> None:
    # Base arguments required for a valid settings object
    kwargs = dict(
        wavelength=1.55,
        window_width=3.0,
        num_modes=2,
        background_material="Air",
    )

    # Override the parametrized dimensional key with a known µm value
    kwargs[key] = 1.0

    settings = get_emode_settings(**kwargs)

    # All dimensional settings should be converted from µm to nm
    assert settings[key] == pytest.approx(1000.0)
    assert settings["num_modes"] == 2
    assert settings["background_material"] == "Air"

```

- Ensure `DIMENSIONAL_SETTINGS` and `pytest` are imported in this test module if they are not already (e.g., `from gplugins.emode.emode import DIMENSIONAL_SETTINGS`).
- If any of the keys in `DIMENSIONAL_SETTINGS` are not valid keyword arguments for `get_emode_settings`, you may need to filter the parametrization to only those keys that `get_emode_settings` accepts.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread gplugins/emode/tests/test_emode.py
Eric Stanton and others added 2 commits July 9, 2026 12:31
Address Sourcery review: cover every DIMENSIONAL_SETTINGS key in the
um-to-nm conversion test, and replace the lambda-based fake session
with an explicit FakeEMode stub class.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@estanton22

Copy link
Copy Markdown
Author

Re: Sourcery's high-level feedback:

  • Stub class in tests: applied in 2be4c7b — the lambda-based fake session is now an explicit FakeEMode stub class.
  • Example duplication (__main__ vs notebook): not taken — a runnable __main__ demo alongside the docs notebook is the existing convention for plugins in this repo (see gplugins/femwell/mode_solver.py and gplugins/meow/test_meow_simulation.py), and a shared example helper would become de facto public API.
  • TypedDict for shape dicts: not taken — the dicts are passed straight through as kwargs to EMode's shape() RPC, whose schema is owned and documented by EMode (https://docs.emodephotonix.com). Mirroring that schema here would drift as EMode adds parameters.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new gplugins.emode plugin that translates gdsfactory cross-sections/layer-stacks into EMode Photonix geometry/settings, and wires the plugin into packaging extras, CI, docs, and an example notebook.

Changes:

  • Add gplugins/emode subpackage with translation helpers and an EMode session wrapper.
  • Add CI-safe unit tests (stub session) and activate generic PDK for the emode test suite.
  • Integrate the plugin into docs (mode-solver page + API reference + notebook), CI matrices, and the emode optional dependency extra.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
uv.lock Locks new emode extra dependency (emodeconnection) and transitive deps.
README.md Mentions EMode as an available (commercial) mode-solver option.
pyproject.toml Adds emode = ["emodeconnection>=1.1.11"] optional extra.
notebooks/emode_01.ipynb Adds an SOI rib waveguide example workflow for EMode.
Makefile Installs/tests/covers the new emode plugin alongside existing plugins.
gplugins/emode/tests/test_emode.py Adds unit tests for unit conversion, shape translation, material matching, and build_waveguide call forwarding.
gplugins/emode/tests/conftest.py Activates the generic gdsfactory PDK for emode tests.
gplugins/emode/tests/__init__.py Adds test package marker for the new plugin.
gplugins/emode/emode.py Implements translation helpers and the EMode.build_waveguide helper on top of emodeconnection.
gplugins/emode/__init__.py Exposes EMode and helper functions as the public plugin API.
docs/plugins_mode_solver.md Documents EMode as a supported mode-solver option and explains installation/license requirements.
docs/api_design.rst Adds autosummary entries for the EMode API surface.
docs/_toc.yml Adds the new EMode notebook to the docs TOC.
docs/_config.yml Excludes the EMode notebook from execution during docs builds.
.pre-commit-config.yaml Extends Ruff hooks to include the new gplugins/emode directory.
.github/workflows/test_code.yml Adds emode to the per-plugin CI test matrix.
.github/workflows/pages.yml Installs the emode extra when building docs on GitHub Pages.
.changelog.d/733.added Adds a changelog fragment for the new plugin.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gplugins/emode/emode.py
Comment on lines +134 to +141
section = next(
(
s
for s in xs.sections
if str(s.layer) in (str(level.layer), str(level.derived_layer))
),
None,
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants