Local-only FDTD + waveguide mode solver for integrated photonics (NumPy core; optional JAX and compiled Rust/CUDA backends).
Project description
photonfdtd
A small, fully-local open-source FDTD + waveguide mode solver written in Python/NumPy. It implements a Yee-grid FDTD time-stepper with CPML absorbing boundaries, dispersive materials, anisotropic subpixel smoothing, and a full-vectorial waveguide mode solver, with an API intentionally similar in spirit to Tidy3D.
This is alpha-stage software (v0.1). The pieces that exist are tested and correct; the pieces that don't, don't. See Status below.
Install
pip install photonfdtd
or from source, either directly from GitHub:
pip install "git+https://github.com/ngpaladi/photonfdtd"
or from a checkout:
git clone https://github.com/ngpaladi/photonfdtd
cd photonfdtd
pip install -e .
The PyPI wheel is pure Python (NumPy core; photonfdtd[jax] for the
differentiable XLA backend). The optional compiled Rust / CUDA backends
(backend="rust", backend="rust-cuda") are built separately from the Rust
sources shipped in the sdist — see benchmarks/README.md.
Releasing (maintainers)
Releases publish to PyPI automatically via
.github/workflows/publish.yml using PyPI
Trusted Publishing (OIDC — no API token stored). To cut a release:
- Bump the version in
pyproject.tomlandsrc/photonfdtd/__init__.py(kept in sync bytests/test_version.py). - Publish a GitHub Release tagged
vX.Y.Zmatching that version. The workflow runs the tests, guards tag-vs-version, builds the sdist + wheel, and publishes.
One-time setup before the first release: on PyPI, add a trusted publisher for
the project (Publishing → add a GitHub Actions publisher) with owner ngpaladi,
repo photonfdtd, workflow publish.yml, environment pypi. Until the project
exists, use PyPI's pending publisher form with the same values.
Quick start
A point dipole radiating in 2D vacuum
import photonfdtd as pf
lam0 = 1.0e-6
freq0 = pf.C_0 / lam0
dx = lam0 / 20
grid = pf.Grid(size=(4e-6, 4e-6), cell_size=dx, pml_layers=(12, 12, 0))
src = pf.PointDipole(
position=(0.0, 0.0),
component="Ez",
waveform=pf.GaussianPulse(freq0=freq0, fwhm=10e-15),
)
mon = pf.FieldMonitor(name="snap", components=("Ez",), interval=20)
sim = pf.Simulation(grid, sources=[src], monitors=[mon], run_time=200e-15)
result = sim.run()
result.fields["snap"]["Ez"] is a (n_frames, ny, nz) array of field snapshots.
Solving a slab waveguide mode
import photonfdtd as pf
clad = pf.Medium.from_index(1.0)
core = pf.Medium.from_index(2.0)
slab = pf.Box(center=(0.0, 0.0), size=(0.5e-6, 0.3e-6), medium=core)
ms = pf.ModeSolver(
size=(4e-6, 3e-6),
cell_size=20e-9,
structures=[slab],
wavelength=1.55e-6,
num_modes=2,
)
result = ms.solve()
print(result.n_eff) # array of effective indices
What v0.2 actually does
- Yee-grid FDTD in 1D / 2D / 3D, with the Courant-stable time step selected automatically.
- CPML absorbing boundaries (Roden & Gedney 2000, kappa = 1) on any number of axes.
- Isotropic dielectric media, non-dispersive or dispersive. Non-dispersive
media stamp a single
eps_rper cell; dispersive media (DispersiveMedium, with.lorentz/.drude/.sellmeierconstructors) carry Lorentz, Drude and Sellmeier poles advanced by the auxiliary-differential-equation (ADE) method during stepping. A cited material library (pf.silica(),pf.silicon(),pf.silicon_nitride(),pf.lithium_niobate(),pf.gold(),pf.silver()) reproduces each source's published index — seedocs/material_data.md. - Anisotropic subpixel smoothing (
Simulation(subpixel=True)): partially filled interface cells get an effective diagonal permittivity tensor (harmonic mean normal to the interface, arithmetic mean tangential), removing the staircase error and restoring second-order boundary accuracy (Farjadpour et al. 2006). Seedocs/accuracy.rst. - Geometry primitives: axis-aligned
Boxand arbitrary-polygonPolySlab(a polygon in xy extruded between two z bounds). - Sources: soft point-dipole (
PointDipole), distributed line/area mode injection (ModeSource), an energy-normalisedSinglePhotonSourcewhose amplitude is set so the launched wavepacket carries exactly $h,\nu$ of total electromagnetic energy, and a moving-chargeChargedParticlecurrent source that emits Cherenkov radiation when it outruns the local phase velocity (seeexamples/04_cherenkov.py). - Monitors: time-domain field snapshots (
FieldMonitor), Poynting flux (FluxMonitor), and a frequency-domainDFTMonitorthat accumulates a running Fourier transform at chosen frequencies so storage scales with the number of frequencies rather than the number of timesteps (routinely 50-1000x smaller than an equivalent time-domain monitor, exact at those frequencies). - Memory efficiency for large volumes: per-array
float32/float64precision, the permittivity grid released during stepping, a shared curl scratch buffer that avoids per-step full-domain temporaries, andFieldMonitor(compression=...), which streams snapshots to disk as per-frame-scaled, quantised, compressed blocks - keeping RAM flat regardless of recording length and shrinking stored data ~10-30x (8-bit) or ~5-7x (16-bit) versus an uncompressed float64 monitor. - Out-of-core stepping (
sim.run(out_of_core=True, tile_cells=...)): for a volume whose fields do not fit in RAM, the six field arrays,ce_fieldand the CPML state are memory-mapped to disk and each timestep sweeps the domain in slabs along one axis, so peak RAM is bounded by the tile size, not the grid. Reproduces the in-core result to machine precision. NumPy backend, point sources andFieldMonitor(incl.compression=) supported; seephotonfdtd.outofcore. - Differentiable PIC inverse design. A substrate/etched-core/cladding stack
driven by a 2-D etch density
ρ(x,y)(EtchedCore) maps — conic filter →tanhprojection → anisotropic subpixel sidewalls → 3-D ε — entirely in JAX, sovalue_and_grad_densityreturnsd(loss)/d(ρ)through the full time evolution (topology optimization; gradient matches finite differences to ~1e-8). Ports are launched one-way withUniModeSource(equivalence-principle current sheets). Seedocs/accuracy.rst. - Mode-decomposition S-parameters (
s_parameters,mode_amplitudes): project the frequency-domain fields on a memory-light port plane (any axis viaDFTMonitor(plane_axis=..., plane_position=...)) onto a solved mode to get complex forward/backward modal amplitudes and hence S-parameters. A mode down a straight lossless guide gives|S21|≈1; the overlap is backend-agnostic (NumPy/CuPy/JAX) so|S_ij|²is differentiable through the adjoint — the PIC inverse-design objective. Seedocs/accuracy.rst. - Full-vectorial waveguide mode solver (
ModeSolver): a finite-difference eigenproblem for the transverse fields on the Yee grid that distinguishes TE from TM and captures high-index-contrast boundary effects. Validated against the exact symmetric-slab dispersion to better than 0.01% on both TE0 and TM0 (Zhu & Brown 2002; Fallahkhair et al. 2008). - gdsfactory adapter (
from_gdsfactory) that reads a layoutComponent, maps its layers onto user-supplied materials, and returns a pre-builtSimulation. - Optional acceleration backends for time-stepping: a Numba JIT path and
a CuPy GPU path (
use_gpu=True). The GPU path uses only generic CuPy array ops, so it runs on either an NVIDIA GPU (CuPy's CUDA build,cupy-cuda12x) or an AMD GPU (CuPy's ROCm build,cupy-rocm-5-0). Both are optional; without them the plain NumPy core runs. - Differentiable JAX backend (
use_jax=True): a pure-functional reimplementation of the Yee + CPML step underjax.lax.scan, JIT-compiled through XLA (CPU/GPU/TPU from one path) and matching the in-core solver to machine precision. Because the stepper is a pure function of the permittivity,pf.jax_value_and_grad_eps(sim, loss)returns the gradient of any monitor-based scalar w.r.t.eps_r— a time-domain adjoint for gradient-based inverse design / topology optimization. Seephotonfdtd.jaxbackend. (pip install "photonfdtd[jax]".)
Running on GPU
JAX is the GPU backend. With use_jax=True the forward run and the
differentiable / reversible adjoints run on the GPU automatically once a CUDA
jaxlib is present — no code change: pip install "jax[cuda12]". Validated on
an NVIDIA RTX 4080 (CUDA 12): forward matches NumPy to ~1e-16, and both adjoints
run on-device. If JAX falls back to CPU with a "cuSPARSE not found"-style error,
the nvidia CUDA libraries just aren't on the loader path — add them, e.g.
export LD_LIBRARY_PATH=$(python -c "import nvidia,glob,os;print(':'.join(glob.glob(os.path.dirname(nvidia.__file__)+'/*/lib')))").
Large differentiable runs fit in GPU memory thanks to gradient checkpointing and
the O(1)-memory reversible adjoint (photonfdtd.reversible).
The CuPy backend (use_gpu=True) is a deprecated legacy path — superseded by
JAX for GPU work, but retained (optional; pip install cupy-cuda12x) for
AMD/ROCm GPUs and for GPU/host/disk out-of-core (use_gpu=True +
run(out_of_core=True)), where fields live on disk and each tile is processed on
the GPU so peak device memory is one tile — a volume larger than GPU memory still
runs (JAX cannot stream to disk).
What v0.2 does not do (yet)
Items below are intentional out-of-scope and tracked in the issue tracker:
- Dispersive media (Lorentz / Drude / Debye) - planned next, via the auxiliary-differential-equation method.
- Full-vectorial 2D mode solving with E and H couplings.
- Anisotropic media.
- Sub-cell averaging for polygon edges.
- Total-field / scattered-field plane wave injection.
Roadmap
- v0.3: dispersive media (Lorentz pole-residue), full-vectorial mode solver, anisotropic materials.
- v0.4: Numba/JAX backend for ~10-100x speed-up.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file photonfdtd-0.12.0.tar.gz.
File metadata
- Download URL: photonfdtd-0.12.0.tar.gz
- Upload date:
- Size: 159.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8fd6c00232b3129238dce5dab8d3291fc1acf49234da5d5b63a07751873776fb
|
|
| MD5 |
1f430f05991e45875bd6e07f500b9f58
|
|
| BLAKE2b-256 |
f9eb5261e52bfcb28c422c6c4313a5a768828730f4e8cc8e773203b26b7bc975
|
Provenance
The following attestation bundles were made for photonfdtd-0.12.0.tar.gz:
Publisher:
publish.yml on ngpaladi/photonfdtd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
photonfdtd-0.12.0.tar.gz -
Subject digest:
8fd6c00232b3129238dce5dab8d3291fc1acf49234da5d5b63a07751873776fb - Sigstore transparency entry: 2238112556
- Sigstore integration time:
-
Permalink:
ngpaladi/photonfdtd@e0b6af36f0badff71a6a0f5486ee7166bd08edb4 -
Branch / Tag:
refs/tags/v0.12.0 - Owner: https://github.com/ngpaladi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e0b6af36f0badff71a6a0f5486ee7166bd08edb4 -
Trigger Event:
release
-
Statement type:
File details
Details for the file photonfdtd-0.12.0-py3-none-any.whl.
File metadata
- Download URL: photonfdtd-0.12.0-py3-none-any.whl
- Upload date:
- Size: 119.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
219221ac2a471f80cf9fea9cad8c2f1ead7a977d6674d1901ec7350b229aba0b
|
|
| MD5 |
b3d2aaadf23f7df2f7e7ca748b374dc4
|
|
| BLAKE2b-256 |
1b5b663c7a9c91b9509031caa8e17d95a2b4cebc76e631c7a336d773a92e2edd
|
Provenance
The following attestation bundles were made for photonfdtd-0.12.0-py3-none-any.whl:
Publisher:
publish.yml on ngpaladi/photonfdtd
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
photonfdtd-0.12.0-py3-none-any.whl -
Subject digest:
219221ac2a471f80cf9fea9cad8c2f1ead7a977d6674d1901ec7350b229aba0b - Sigstore transparency entry: 2238112698
- Sigstore integration time:
-
Permalink:
ngpaladi/photonfdtd@e0b6af36f0badff71a6a0f5486ee7166bd08edb4 -
Branch / Tag:
refs/tags/v0.12.0 - Owner: https://github.com/ngpaladi
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e0b6af36f0badff71a6a0f5486ee7166bd08edb4 -
Trigger Event:
release
-
Statement type: