EDA- and solver-agnostic 3D FDTD simulation framework for photonic layouts: GDS in, S-parameters, fields, and compact models out - tidy3d, Lumerical, or beamz behind one API.
One component, one technology file, one SimulationSpec, any engine: EDA-agnostic on the front (KLayout/SiEPIC, gdsfactory, raw GDS), solver-agnostic on the back. Beyond the S-matrix you also get field and mode visualizations, physics checks (reciprocity/passivity), mesh-convergence and cross-engine validation, and one-call export to compact-model formats (Touchstone .sNp, Lumerical INTERCONNECT .dat, HDF5) for circuit simulation.
solver = get_solver("tidy3d" | "lumerical" | "beamz")(component, tech, spec)
smatrix = solver.run()All images are real solver output.
Every solver setup renders as an interactive 3D scene — the layer stack with per-layer colors, port cones, field-monitor planes, and the domain box. Orbit, zoom, click an object for its material and z-extent, toggle groups from the legend. Play with it live in the docs (also embedded in examples 01 and 11):
from gds_fdtd.viewer3d import show_3d
show_3d(solver) # notebooks + docs; save_3d(...) writes a shareable page- Bring your own engine: implement four methods and any FDTD engine plugs in with full S-matrix export, physics checks, caching, CLI, and a free conformance test suite — the guide: docs/adding_a_solver.md.
- Solver-agnostic engine registry:
get_solver("tidy3d" | "lumerical" | "beamz")— identical(component, technology, SimulationSpec)in, identicalSMatrixout. Third-party engines plug in via entry points.validate()/build()/estimate()are always offline and free; onlyrun()spends credits/licenses/compute. - Layout ingestion: raw GDS via KLayout with SiEPIC pin/devrec conventions, SiEPIC PDK cells, and gdsfactory (>= 9) components — ports auto-detected, never hand-placed.
- Validated technology files: the layer stack is a pydantic-validated YAML (bad files fail with the offending key named). Materials can carry per-solver entries or a neutral refractiveindex.info reference (
rii: {shelf, book, page}), resolved offline from a local database copy. - Canonical S-matrix: one
SMatrixtype with NaN-aware partial matrices, reciprocity/passivity/power-balance checks, and I/O to Lumerical INTERCONNECT.dat, Touchstone.sNp(scikit-rf compatible), HDF5/npz, plus plotting. - Cross-validated engines (see SOLVER_STATUS.md for per-engine last-verified dates): the tidy3d (>= 2.11, cloud) and Lumerical (2024/2025, local) adapters were validated live against each other on identical geometry — within 0.0033 dB, with the free beamz engine (JAX, CPU/GPU) inside 0.052 dB of both; the agreement is locked into CI via recorded artifacts.
- Interactive 3D viewer:
show_3d(solver)renders the extruded layer stack, ports, field-monitor planes, and simulation domain as an orbitable, clickable three.js scene — in notebooks and in the documentation gallery alike;render_staticdraws the same scene without JavaScript. - Multimode/dual-polarization simulations on the engines that support them (tidy3d, Lumerical).
- Serializable jobs + CLI: every simulation is a JSON
JobSpec;gds-fdtd validate|build|estimate|run|convert|solversdrives it from the shell, andSubprocessBackendruns sweeps crash-isolated and in parallel. Secrets stay in the environment — job files are safe to ship to a cluster or cloud runner (docs/remote_compute.md). - Convergence sweeps, caching, cross-solver validation:
convergence.sweep()steps anySimulationSpecfield and recommends the converged value;run_cached()hashes the full job (geometry + technology + spec + engine version) so repeat runs are free;validation.validate_across()quantifies worst-case |ΔS| between engines on the same job.
| engine | execution | cost | install |
|---|---|---|---|
| Tidy3D >= 2.11 | cloud | FlexCredits | pip install gds_fdtd[tidy3d] |
| Ansys Lumerical FDTD 2024/2025 | local | license | Lumerical install + lumapi on path |
| beamz >= 0.4.3, < 0.5 | local (JAX, CPU/GPU) | free | pip install gds_fdtd[beamz] |
A guided path from "load a layout" to "run it on any engine and read the
S-parameters" — paired .py (jupytext) + executed .ipynb. See
examples/README.md for the full tour.
| # | example | shows | engine |
|---|---|---|---|
| 00 | 00_quickstart/ |
layout → S-matrix in ten lines | beamz (free) |
| 01 | 01_layout_to_component/ |
load a GDS / gdsfactory cell, auto-detect ports, read the geometry | none |
| 02 | 02_technology/ |
materials, refractiveindex.info vs shipped models, the vertical layer stack |
none |
| 02b | 02_technology/ |
feed one refractiveindex.info model (full complex n+ik) into every engine |
tidy3d-local + recorded |
| 03 | 03_first_simulation/ |
the full flow end-to-end: geometry → permittivity → build → run → S-params → fields | beamz (free) |
| 04 | 04_reading_results/ |
SMatrix: insertion loss, crosstalk, phase, reciprocity/passivity, Touchstone/HDF5/npz I/O |
none |
| 05 | 05_fields_and_modes/ |
waveguide mode profiles, effective indices, permittivity cross-sections | tidy3d-local (free) |
| 05b | 05_fields_and_modes/ |
field monitors: axes, pinned positions, recorded wavelengths, plot_monitor_planes; the escalator side view |
recorded (tidy3d) |
| 06 | 06_convergence_and_caching/ |
mesh-convergence sweeps, run_cached (repeat runs free), and cross-engine validation where converged ≠ correct |
beamz + recorded |
| 07 | 07_choosing_an_engine/ |
the identical job on beamz / tidy3d / Lumerical, and how they agree | all three |
| 08 | 08_frontends/ |
any EDA in, any engine out: gdsfactory / SiEPIC / raw-GDS frontends, then the frontend × engine matrix | mixed |
| 09 | 09_cli_and_jobs/ |
the gds-fdtd CLI and serializable JobSpec for remote/batch compute |
none |
| 10 | 10_cookbook/ |
reference devices with known-good S-params — the Si→SiN escalator on the free engine, cross-checked against recorded tidy3d/Lumerical | beamz + recorded |
| 10b | 10_cookbook/ |
polarization splitter and splitter-rotator from gdsfactory: TE/TM modes, multi-mode S-params, per-polarization fields | recorded (2 engines) |
| 11 | 11_bragg_grating/ |
a 95 µm Bragg grating: stopband spectrum + the field reflecting in-band and passing out-of-band | recorded (tidy3d) |
pip install gds-fdtd # core
pip install "gds-fdtd[all]" # + every engine and layout frontendFrom source, editable:
git clone https://github.com/SiEPIC/gds_fdtd.git && cd gds_fdtd
pip install -e ".[all]" # everything, editable
pip install -e ".[all,dev]" # + test and lint tools, for contributingLumerical needs no extra; the adapter finds lumapi from your local Lumerical install.
To pick plugins individually, each is its own extra:
| extra | adds |
|---|---|
tidy3d |
Tidy3D cloud solver |
beamz |
beamz open-source JAX solver |
gdsfactory |
gdsfactory frontend |
siepic |
SiEPIC / KLayout frontend |
prefab |
PreFab lithography prediction |
engines |
tidy3d + beamz |
all |
every plugin above |
dev |
test and lint tooling (for contributing) |
Requires Python ≥ 3.11; core runtime deps (numpy, matplotlib, shapely, PyYAML, klayout, pydantic) install automatically.
If you've installed the dev dependencies, you can run the test suite with:
pytest --cov=gds_fdtd testsgit clone https://github.com/SiEPIC/gds_fdtd.git
cd gds_fdtd
pip install -e .[dev] # or: uv sync --extra dev
# install the git hooks (uses the standard .pre-commit-config.yaml;
# prek is a fast drop-in for pre-commit)
uv tool install prek && prek installCanonical dev tasks live in the justfile:
just test # tests with coverage
just lint # ruff check + format check (what CI runs)
just fix # auto-fix lint + formatting
just docs # build documentation
just gate # quick lint+test gateThe version is derived from git tags via hatch-vcs — there is nothing to bump and no
version string in the source. To release:
git tag v0.6.2
git push --tagsThe release.yml workflow then verifies the tagged commit passed CI, builds and inspects the
package, publishes to PyPI via Trusted Publishing (with PEP 740 attestations), and creates a
GitHub Release with auto-generated notes (categorized by PR labels — see
.github/release.yml).





