Add EMode plugin#733
Conversation
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>
Reviewer's GuideAdd 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 integrationsequenceDiagram
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
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
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.
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>
There was a problem hiding this comment.
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 ingplugins/emode/emode.pyandnotebooks/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 untypeddict[str, Any]; if the shape schema is stable, introducing a lightweightTypedDictor dataclass for shapes would make the mapping to EMode’sshape()arguments clearer and easier to maintain. - The
_fake_sessionintest_emode.pyuses anonymous lambdas attached to anEModeinstance for recording calls; replacing this with a simple stub class with explicitsettings,shape, andgetmethods 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
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>
|
Re: Sourcery's high-level feedback:
|
There was a problem hiding this comment.
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/emodesubpackage with translation helpers and anEModesession 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
emodeoptional 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.
| section = next( | ||
| ( | ||
| s | ||
| for s in xs.sections | ||
| if str(s.layer) in (str(level.layer), str(level.derived_layer)) | ||
| ), | ||
| None, | ||
| ) |
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/emodesubpackage that bridges gdsfactory and EMode:get_shapes_from_layer_stackconverts a gdsfactoryLayerStack+CrossSectioninto 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), andget_emode_settingsconverts gdsfactory's um units to EMode's nm units.EModesession class: subclassesemodeconnection.EModeand addsbuild_waveguide(cross_section, layer_stack, **settings). Any EMode function is callable as a method on the session (FDM(),EME(),report(),plot(), ...).emodeextra: depends only on the pip-installable, MIT-friendlyemodeconnectionclient (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.notebooks/emode_01.ipynbSOI rib waveguide example.Testing
Runs in CI (added to the
test_code.ymlmatrix; only needspip install .[emode,dev], no EMode installation or license):gplugins/emode/tests/test_emode.pycovers unit conversion, layer-stack-to-shape translation, cross-section width/offset matching, mesh-order-to-priority mapping, material name matching, error handling, andbuild_waveguideagainst a stub session that records calls instead of launching the application. Collection is guarded withpytest.importorskip("emodeconnection").Requires a full EMode installation + license (not run in CI):
notebooks/emode_01.ipynband the__main__block ingplugins/emode/emode.py. The notebook is indocs/_config.ymlexecute_notebooksexclude 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:
Enhancements:
Build:
CI:
Documentation:
Tests: