feat(meep): standalone eigenmode solver for 1D slab and 2D cross-sections#174
Open
benvial wants to merge 35 commits into
Open
feat(meep): standalone eigenmode solver for 1D slab and 2D cross-sections#174benvial wants to merge 35 commits into
benvial wants to merge 35 commits into
Conversation
Expose ModeResult model and solve_slab_mode / solve_cross_section_mode functions with lazy MEEP import. Add Simulation.solve_mode() wrapper and _resolve_stack_and_materials() for lighter material resolution separate from FDTD build_config. Closes gdsfactory#158
Demonstrates solve_slab_mode(), solve_cross_section_mode(), and Simulation.solve_mode() with slab, port, position, and wavelength sweep.
…e solver notebook
- Monkeypatch _import_meep in no-meep error-path tests so they pass when meep is actually installed - test_delegates_to_mode_solver now tests real delegation path, asserting ModeResult return with valid n_eff - Extract _poly_to_shapely helper; add component.dup() fallback for polygon retrieval - Replace mode.get_field() with point-scan amplitude extraction for 1D Z field profiles
…index - mode_z_grid(stack, n_points) — z-axis coords on stack midpoint - refractive_index_profile(stack, z_grid, wavelength) — n(z) via resolve_materials - compute_group_index kwarg on solve_slab_mode/solve_cross_section_mode for two-pass Bloch-periodic group velocity from MEEP - Export new utils from gsim.meep - Add unit tests (TestModeZGrid, TestRefractiveIndexProfile) + integration test - Rewrite notebook in 5 parts with analytical SOI slab benchmark - Fix preexisting ruff PT012/cp1252 issues
- _validate_mode() warns on leaky/radiation modes (n_eff < min_n_clad, slab kz != 0, n_group < 0) - ModeResult.kdom docstring: 2pi/um -> MPB k-units (n_eff/lambda) - Analytical solver: reject spurious roots from tan pole crossings - Notebook: switch multi-band examples to SOI slab, add V-parameter guide
Map `meep.native` logger level to `mp.verbosity()` (WARNING+\u21920, INFO\u21921, DEBUG\u21922), wire `run(verbose="full")` override, and snap mode-solver cell dimensions to integer pixel counts to eliminate grid-volume warnings. - SimConfig: add `meep_verbosity` field (default 0, quiet) - build_config: resolve verbosity from logger + verbose param override - runner template: apply `mp.verbosity()` from config JSON - _import_meep: apply `mp.verbosity()` from logger level - mode_solver: round cell_size to integer pixel counts
ty: replace `object` with `Any` for MEEP types, fix dict invariance jupytext: sync palace_cpw notebooks, fix version in headers
_add_domain_bbox, _collect_pec_surface_lines, and _line_on_domain_boundary each called gmsh.model.* without checking gmsh.isInitialized(). When run under monkeypatched unit tests (no live gmsh session), every call emitted 'Error: Gmsh has not been initialized' to stderr — up to 6 per test. Added early-return guards that short-circuit to safe defaults (zero bbox, empty list, False) when gmsh has not been initialized.
This test calls sim.solve_mode() which requires a local MEEP installation. It was incorrectly placed in a class claiming 'no meep required'.
get_eigenmode always provides mode.group_velocity at Gamma point. The two-pass compute_group_index path was unnecessary — same result as single-pass. Removed ~90 lines of dead code.
- _import_meep() now called before None guards in solve_slab_mode and solve_cross_section_mode so ImportError surfaces correctly - Add gmsh:geometrical cell data to test MSH fixtures to suppress meshio "Appending zeros" warnings - Convert class-scoped test fixtures to @classmethod per pytest 10 deprecation
benvial
requested review from
cdaunt,
flaport,
joamatab,
nikosavola and
vvahidd
as code owners
June 25, 2026 12:03
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #174 +/- ##
==========================================
- Coverage 63.27% 58.19% -5.09%
==========================================
Files 67 69 +2
Lines 9757 11200 +1443
Branches 1970 2332 +362
==========================================
+ Hits 6174 6518 +344
- Misses 2900 3950 +1050
- Partials 683 732 +49 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- _compute_eigenmode: sample fields on 2D (nz, nx) grid instead of 1D Z-scan - Add mode_x_grid() utility for X-axis coordinate reconstruction - Export mode_x_grid from gsim.meep - Notebook: add 2D cross-section visualization section (pcolormesh, contours, multi-component grid) - Tests: add TestModeXGrid class - gitignore: add .kilo/
- _meep_mode_solver.py -> _meep_mode_solver_soi.py (SOI strip waveguide, full tutorial) - New _meep_mode_solver_rib.py: rib waveguide example with 2D XZ field maps - Rib: dual Si layers (slab 70nm + rib 150nm), manual component build, n_eff + profile
benvial
marked this pull request as draft
June 25, 2026 12:45
Remove cross-section mode sections, mode_x_grid import, 2D XZ plots. SOI notebook focuses on slab mode solving exclusively. 2D cross-section content lives in _meep_mode_solver_rib.py.
- Add pml_thickness param to builder cells and public API (default 0.0) - Expose eigensolver_tol on solve_slab_mode/solve_cross_section_mode (default 1e-6, up from hardcoded 1e-12) - Add z_margin param for cell padding above/below stack - Remove broken n_eff_guess (kpoint is Bloch boundary, not MPB guess) - Add _layer_has_any_polygon helper for background layer fallback - Fix rib notebook: add slab layer polygon, port width, layer stack
Solve once per band (4 calls), then plot all 6 field components from stored results. Avoids ~20 redundant init_sim/get_eigenmode/reset_meep cycles.
- Add solve_slab_modes: builds cell once, solves N bands -> 2.4x faster than N separate solve_slab_mode calls - Notebook: res=64->32, use solve_slab_modes for multi-band sections
- MaterialData cache (64-entry LRU) avoids redundant Sellmeier eval - solve_slab_wavelength_sweep() reuses cell geometry across lambda - Reduce slab x_span from 4.0um to 2/resolution (2px) — MPB minimum - mode_z_grid / mode_x_grid / mode_y_grid gain pml_thickness param - Notebook: loops replaced with batch solver calls - Runtime: 16.4s -> 5.8s (3x), results unchanged
… into feat/mode-solver # Conflicts: # nbs/palace_cpw_lumped.ipynb
Cell builders accept (bottom, top) tuple z_margin with shifted z_center. mode_z_grid returns absolute coordinates instead of MEEP-frame. refractive_index_profile accepts absolute z_grid. Solver functions pass asymmetric margins directly instead of max() coercion. field_z_grid converted absolute->MEEP internally. Closes z-margin-asymmetric-fix plan.
Remove port_width+2.0 auto-default in solve_cross_section_mode and Simulation.solve_mode. Span must be passed explicitly - no more silent defaults that hide solver configuration from the caller.
Replace Simulation.solve_mode() with declarative ModeSolver Pydantic model and solve_modes() execution method. ModeSolver provides validated fields, fluent chaining, and shared-cell optimization for slab wavelength sweeps. ModeSweepResult wraps results with .at(), .band(), .to_dict() accessors. - Add ModeSolver model to gsim.meep.models.api with 13 fields + validators - Add mode_solver field to Simulation with default_factory - Replace solve_mode() with solve_modes() dispatching to low-level solvers - Add ModeSweepResult wrapper in gsim.meep.results - Export ModeSolver, ModeSweepResult from gsim.meep - 27 model unit tests + 12 integration tests (meep_local) - Update notebook to use new API
ModeResult gains 7 optional grid/context fields populated by solvers. plot_mode() renders 1D/2D field profiles with index overlay, auto component detection, norm transforms, shared colorbar, ax injection. plot_index() renders n(z) or n(y,z) from stored context. ModeSweepResult delegates via plot_mode(wavelength, band) and plot_index(wavelength).
Unintended 0.5 µm defaults caused mode-field z-grid to extend past the layer-stack boundaries without explicit overrides.
- 2D cross-section builders staircase-approximate sidewall_angle via stacked mp.Block sub-layers (ceil(h*res) layers) - LiNbO3 registered as uniaxial in MATERIALS_DB (Zelmon 1997) with per-axis DispersionModel.axis; TFLN ridge gets sidewall_angle=17 - TFLN/rib notebooks refactored to declarative Simulation+ModeSolver - plot_mode: rm plt.close, auto-colormap by norm, index overlay under field data with alpha=0.25 - fix solve_modes ignoring mode_solver.background_material
Replace stacked mp.Block sub-layer approximation with single mp.Prism per layer interval using native sidewall_angle parameter. Positive angle = top-narrower, matches previous staircase convention.
plot_mode(geometry=True) used build_geometry_model which reads the gdsfactory PDK layer stack. Components with custom GDS layers (e.g. TFLN ridge (2,0), slab (1,0)) had no matching PDK named layers, producing an empty geometry overlay. Replace with build_cross_section_rectangles that extracts polygon intersections directly from the component GDS data + gsim LayerStack. Wire domain_config into ModeResult so the guard check is satisfied.
Geometry has no z_crop field; _apply_z_crop was called with 0 args (wrong signature). Real z-crop runs in build_config(). Fixes ty hook.
Replace low-level solve_slab_mode/solve_slab_modes calls with declarative meep.Simulation() + mode_solver fluent API throughout. Deduplicate _soi_slab/_make_soi_stack. ~50% line reduction.
- Replace hardcoded n_si/n_sio2 with Sellmeier dispersion from gsim - Add analytic group index via implicit differentiation of char. eq. - Add wavelength sweep and thickness sweep Meep-vs-analytical plots - Move comparison cells after analytical fn definitions
benvial
marked this pull request as ready for review
July 16, 2026 10:26
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Exposes MEEP eigenmode solving as a standalone callable in
src/gsim/meep/, no longer requiring the full cloud FDTD job.Closes #158.
Changes
Core —
src/gsim/meep/mode_solver.py(775 lines)solve_slab_mode(stack, wavelength, ...)solve_cross_section_mode(component, stack, port=..., ...)mode_z_grid(stack, n_points)refractive_index_profile(stack, z_grid, wavelength)All solvers return
ModeResult(n_eff,fields,kdom,n_group,band_num,parity).Integration —
Simulation.solve_mode()High-level wrapper on
Simulationthat resolves stack/materials via the existing three-tier pipeline, then delegates to slab or cross-section solver.ModeResultmodel (src/gsim/meep/models/results.py)Pydantic model with
n_eff,fields(complex field arrays by component),kdom,n_group, metadata.Tests —
tests/meep/test_mode_solver.py(571 lines)ModeResultvalidation, import guard,mode_z_grid,refractive_index_profile(no MEEP required)meep_local, deselected by default)Simulation.solve_mode()delegation testSide fixes
palace/mesh/generator.py)Example notebook —
nbs/_meep_mode_solver.pyDemonstrates slab mode solving, cross-section mode solving at a port, field profile and refractive index overlay plots.