photonfdtd 0.12.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- photonfdtd-0.12.0/.gitignore +15 -0
- photonfdtd-0.12.0/LICENSE +21 -0
- photonfdtd-0.12.0/PKG-INFO +254 -0
- photonfdtd-0.12.0/README.md +221 -0
- photonfdtd-0.12.0/benchmarks/README.md +135 -0
- photonfdtd-0.12.0/pyproject.toml +64 -0
- photonfdtd-0.12.0/rust/Cargo.lock +348 -0
- photonfdtd-0.12.0/rust/Cargo.toml +26 -0
- photonfdtd-0.12.0/rust/src/adi.rs +375 -0
- photonfdtd-0.12.0/rust/src/blocked.rs +350 -0
- photonfdtd-0.12.0/rust/src/cuda.rs +1116 -0
- photonfdtd-0.12.0/rust/src/kernel.rs +410 -0
- photonfdtd-0.12.0/rust/src/lib.rs +152 -0
- photonfdtd-0.12.0/src/photonfdtd/__init__.py +84 -0
- photonfdtd-0.12.0/src/photonfdtd/adapters.py +190 -0
- photonfdtd-0.12.0/src/photonfdtd/adi.py +337 -0
- photonfdtd-0.12.0/src/photonfdtd/boundaries.py +97 -0
- photonfdtd-0.12.0/src/photonfdtd/constants.py +8 -0
- photonfdtd-0.12.0/src/photonfdtd/design.py +175 -0
- photonfdtd-0.12.0/src/photonfdtd/eim.py +319 -0
- photonfdtd-0.12.0/src/photonfdtd/eme.py +267 -0
- photonfdtd-0.12.0/src/photonfdtd/geometry.py +154 -0
- photonfdtd-0.12.0/src/photonfdtd/grid.py +111 -0
- photonfdtd-0.12.0/src/photonfdtd/jaxbackend.py +773 -0
- photonfdtd-0.12.0/src/photonfdtd/materials.py +268 -0
- photonfdtd-0.12.0/src/photonfdtd/memory.py +165 -0
- photonfdtd-0.12.0/src/photonfdtd/mode.py +387 -0
- photonfdtd-0.12.0/src/photonfdtd/monitors.py +191 -0
- photonfdtd-0.12.0/src/photonfdtd/outofcore.py +424 -0
- photonfdtd-0.12.0/src/photonfdtd/reversible.py +248 -0
- photonfdtd-0.12.0/src/photonfdtd/reversible_pml.py +287 -0
- photonfdtd-0.12.0/src/photonfdtd/rustbackend.py +461 -0
- photonfdtd-0.12.0/src/photonfdtd/simulation.py +1459 -0
- photonfdtd-0.12.0/src/photonfdtd/smatrix.py +118 -0
- photonfdtd-0.12.0/src/photonfdtd/smoothing.py +172 -0
- photonfdtd-0.12.0/src/photonfdtd/sources.py +544 -0
- photonfdtd-0.12.0/src/photonfdtd/storage.py +219 -0
- photonfdtd-0.12.0/tests/test_backend_dispatch.py +75 -0
- photonfdtd-0.12.0/tests/test_backends.py +196 -0
- photonfdtd-0.12.0/tests/test_design.py +84 -0
- photonfdtd-0.12.0/tests/test_dispersion.py +131 -0
- photonfdtd-0.12.0/tests/test_eim_eme_adi.py +218 -0
- photonfdtd-0.12.0/tests/test_jax.py +174 -0
- photonfdtd-0.12.0/tests/test_jax_accuracy.py +139 -0
- photonfdtd-0.12.0/tests/test_memory.py +306 -0
- photonfdtd-0.12.0/tests/test_memory_planner.py +49 -0
- photonfdtd-0.12.0/tests/test_mode_vectorial.py +144 -0
- photonfdtd-0.12.0/tests/test_physics.py +216 -0
- photonfdtd-0.12.0/tests/test_reversible.py +121 -0
- photonfdtd-0.12.0/tests/test_rust.py +238 -0
- photonfdtd-0.12.0/tests/test_smatrix.py +149 -0
- photonfdtd-0.12.0/tests/test_smoke.py +84 -0
- photonfdtd-0.12.0/tests/test_smoothing.py +137 -0
- photonfdtd-0.12.0/tests/test_version.py +47 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ngpaladi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: photonfdtd
|
|
3
|
+
Version: 0.12.0
|
|
4
|
+
Summary: Local-only FDTD + waveguide mode solver for integrated photonics (NumPy core; optional JAX and compiled Rust/CUDA backends).
|
|
5
|
+
Project-URL: Homepage, https://github.com/ngpaladi/photonfdtd
|
|
6
|
+
Project-URL: Issues, https://github.com/ngpaladi/photonfdtd/issues
|
|
7
|
+
Author: ngpaladi
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: electromagnetics,fdtd,mode-solver,photonics,simulation
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: matplotlib>=3.7
|
|
19
|
+
Requires-Dist: numpy>=1.24
|
|
20
|
+
Requires-Dist: scipy>=1.10
|
|
21
|
+
Provides-Extra: compress
|
|
22
|
+
Requires-Dist: zstandard>=0.21; extra == 'compress'
|
|
23
|
+
Provides-Extra: docs
|
|
24
|
+
Requires-Dist: sphinx-rtd-theme>=3; extra == 'docs'
|
|
25
|
+
Requires-Dist: sphinx>=7; extra == 'docs'
|
|
26
|
+
Provides-Extra: jax
|
|
27
|
+
Requires-Dist: jax>=0.4; extra == 'jax'
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: numba>=0.59; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest>=7.4; extra == 'test'
|
|
31
|
+
Requires-Dist: zstandard>=0.21; extra == 'test'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# photonfdtd
|
|
35
|
+
|
|
36
|
+
A small, fully-local open-source FDTD + waveguide mode solver written in
|
|
37
|
+
Python/NumPy. It implements a Yee-grid FDTD time-stepper with CPML
|
|
38
|
+
absorbing boundaries, dispersive materials, anisotropic subpixel smoothing,
|
|
39
|
+
and a full-vectorial waveguide mode solver, with an API intentionally similar
|
|
40
|
+
in spirit to Tidy3D.
|
|
41
|
+
|
|
42
|
+
This is alpha-stage software (v0.1). The pieces that exist are tested and
|
|
43
|
+
correct; the pieces that don't, don't. See *Status* below.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install photonfdtd
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
or from source, either directly from GitHub:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install "git+https://github.com/ngpaladi/photonfdtd"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
or from a checkout:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/ngpaladi/photonfdtd
|
|
61
|
+
cd photonfdtd
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The PyPI wheel is pure Python (NumPy core; `photonfdtd[jax]` for the
|
|
66
|
+
differentiable XLA backend). The optional compiled Rust / CUDA backends
|
|
67
|
+
(`backend="rust"`, `backend="rust-cuda"`) are built separately from the Rust
|
|
68
|
+
sources shipped in the sdist — see [`benchmarks/README.md`](benchmarks/README.md).
|
|
69
|
+
|
|
70
|
+
### Releasing (maintainers)
|
|
71
|
+
|
|
72
|
+
Releases publish to PyPI automatically via
|
|
73
|
+
[`.github/workflows/publish.yml`](.github/workflows/publish.yml) using PyPI
|
|
74
|
+
**Trusted Publishing** (OIDC — no API token stored). To cut a release:
|
|
75
|
+
|
|
76
|
+
1. Bump the version in `pyproject.toml` **and** `src/photonfdtd/__init__.py`
|
|
77
|
+
(kept in sync by `tests/test_version.py`).
|
|
78
|
+
2. Publish a GitHub Release tagged `vX.Y.Z` matching that version. The workflow
|
|
79
|
+
runs the tests, guards tag-vs-version, builds the sdist + wheel, and
|
|
80
|
+
publishes.
|
|
81
|
+
|
|
82
|
+
One-time setup before the first release: on PyPI, add a *trusted publisher* for
|
|
83
|
+
the project (Publishing → add a GitHub Actions publisher) with owner `ngpaladi`,
|
|
84
|
+
repo `photonfdtd`, workflow `publish.yml`, environment `pypi`. Until the project
|
|
85
|
+
exists, use PyPI's *pending publisher* form with the same values.
|
|
86
|
+
|
|
87
|
+
## Quick start
|
|
88
|
+
|
|
89
|
+
### A point dipole radiating in 2D vacuum
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import photonfdtd as pf
|
|
93
|
+
|
|
94
|
+
lam0 = 1.0e-6
|
|
95
|
+
freq0 = pf.C_0 / lam0
|
|
96
|
+
dx = lam0 / 20
|
|
97
|
+
|
|
98
|
+
grid = pf.Grid(size=(4e-6, 4e-6), cell_size=dx, pml_layers=(12, 12, 0))
|
|
99
|
+
src = pf.PointDipole(
|
|
100
|
+
position=(0.0, 0.0),
|
|
101
|
+
component="Ez",
|
|
102
|
+
waveform=pf.GaussianPulse(freq0=freq0, fwhm=10e-15),
|
|
103
|
+
)
|
|
104
|
+
mon = pf.FieldMonitor(name="snap", components=("Ez",), interval=20)
|
|
105
|
+
|
|
106
|
+
sim = pf.Simulation(grid, sources=[src], monitors=[mon], run_time=200e-15)
|
|
107
|
+
result = sim.run()
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`result.fields["snap"]["Ez"]` is a `(n_frames, ny, nz)` array of field snapshots.
|
|
111
|
+
|
|
112
|
+
### Solving a slab waveguide mode
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
import photonfdtd as pf
|
|
116
|
+
|
|
117
|
+
clad = pf.Medium.from_index(1.0)
|
|
118
|
+
core = pf.Medium.from_index(2.0)
|
|
119
|
+
slab = pf.Box(center=(0.0, 0.0), size=(0.5e-6, 0.3e-6), medium=core)
|
|
120
|
+
|
|
121
|
+
ms = pf.ModeSolver(
|
|
122
|
+
size=(4e-6, 3e-6),
|
|
123
|
+
cell_size=20e-9,
|
|
124
|
+
structures=[slab],
|
|
125
|
+
wavelength=1.55e-6,
|
|
126
|
+
num_modes=2,
|
|
127
|
+
)
|
|
128
|
+
result = ms.solve()
|
|
129
|
+
print(result.n_eff) # array of effective indices
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## What v0.2 actually does
|
|
133
|
+
|
|
134
|
+
- **Yee-grid FDTD** in 1D / 2D / 3D, with the Courant-stable time step
|
|
135
|
+
selected automatically.
|
|
136
|
+
- **CPML** absorbing boundaries (Roden & Gedney 2000, kappa = 1) on any
|
|
137
|
+
number of axes.
|
|
138
|
+
- **Isotropic dielectric media, non-dispersive or dispersive.** Non-dispersive
|
|
139
|
+
media stamp a single `eps_r` per cell; dispersive media (`DispersiveMedium`,
|
|
140
|
+
with `.lorentz` / `.drude` / `.sellmeier` constructors) carry Lorentz, Drude
|
|
141
|
+
and Sellmeier poles advanced by the auxiliary-differential-equation (ADE)
|
|
142
|
+
method during stepping. A cited material library (`pf.silica()`,
|
|
143
|
+
`pf.silicon()`, `pf.silicon_nitride()`, `pf.lithium_niobate()`, `pf.gold()`,
|
|
144
|
+
`pf.silver()`) reproduces each source's published index — see
|
|
145
|
+
[`docs/material_data.md`](docs/material_data.md).
|
|
146
|
+
- **Anisotropic subpixel smoothing** (`Simulation(subpixel=True)`): partially
|
|
147
|
+
filled interface cells get an effective diagonal permittivity tensor
|
|
148
|
+
(harmonic mean normal to the interface, arithmetic mean tangential), removing
|
|
149
|
+
the staircase error and restoring second-order boundary accuracy
|
|
150
|
+
(Farjadpour et al. 2006). See [`docs/accuracy.rst`](docs/accuracy.rst).
|
|
151
|
+
- **Geometry primitives**: axis-aligned `Box` and arbitrary-polygon
|
|
152
|
+
`PolySlab` (a polygon in xy extruded between two z bounds).
|
|
153
|
+
- **Sources**: soft point-dipole (`PointDipole`), distributed line/area
|
|
154
|
+
mode injection (`ModeSource`), an energy-normalised `SinglePhotonSource`
|
|
155
|
+
whose amplitude is set so the launched wavepacket carries exactly
|
|
156
|
+
$h\,\nu$ of total electromagnetic energy, and a moving-charge
|
|
157
|
+
`ChargedParticle` current source that emits **Cherenkov radiation** when
|
|
158
|
+
it outruns the local phase velocity (see `examples/04_cherenkov.py`).
|
|
159
|
+
- **Monitors**: time-domain field snapshots (`FieldMonitor`), Poynting flux
|
|
160
|
+
(`FluxMonitor`), and a frequency-domain `DFTMonitor` that accumulates a
|
|
161
|
+
running Fourier transform at chosen frequencies so storage scales with the
|
|
162
|
+
number of frequencies rather than the number of timesteps (routinely
|
|
163
|
+
50-1000x smaller than an equivalent time-domain monitor, exact at those
|
|
164
|
+
frequencies).
|
|
165
|
+
- **Memory efficiency for large volumes**: per-array `float32`/`float64`
|
|
166
|
+
precision, the permittivity grid released during stepping, a shared curl
|
|
167
|
+
scratch buffer that avoids per-step full-domain temporaries, and
|
|
168
|
+
`FieldMonitor(compression=...)`, which streams snapshots to disk as
|
|
169
|
+
per-frame-scaled, quantised, compressed blocks - keeping RAM flat regardless
|
|
170
|
+
of recording length and shrinking stored data ~10-30x (8-bit) or ~5-7x
|
|
171
|
+
(16-bit) versus an uncompressed float64 monitor.
|
|
172
|
+
- **Out-of-core stepping** (`sim.run(out_of_core=True, tile_cells=...)`): for a
|
|
173
|
+
volume whose fields do not fit in RAM, the six field arrays, `ce_field` and
|
|
174
|
+
the CPML state are memory-mapped to disk and each timestep sweeps the domain
|
|
175
|
+
in slabs along one axis, so **peak RAM is bounded by the tile size, not the
|
|
176
|
+
grid**. Reproduces the in-core result to machine precision. NumPy backend,
|
|
177
|
+
point sources and `FieldMonitor` (incl. `compression=`) supported; see
|
|
178
|
+
`photonfdtd.outofcore`.
|
|
179
|
+
- **Differentiable PIC inverse design.** A substrate/etched-core/cladding stack
|
|
180
|
+
driven by a 2-D etch density `ρ(x,y)` (`EtchedCore`) maps — conic filter →
|
|
181
|
+
`tanh` projection → anisotropic subpixel sidewalls → 3-D ε — entirely in JAX,
|
|
182
|
+
so `value_and_grad_density` returns `d(loss)/d(ρ)` through the full time
|
|
183
|
+
evolution (topology optimization; gradient matches finite differences to
|
|
184
|
+
~1e-8). Ports are launched one-way with `UniModeSource` (equivalence-principle
|
|
185
|
+
current sheets). See [`docs/accuracy.rst`](docs/accuracy.rst).
|
|
186
|
+
- **Mode-decomposition S-parameters** (`s_parameters`, `mode_amplitudes`):
|
|
187
|
+
project the frequency-domain fields on a memory-light port plane (any axis via
|
|
188
|
+
`DFTMonitor(plane_axis=..., plane_position=...)`) onto a solved mode to get
|
|
189
|
+
complex forward/backward modal amplitudes and hence S-parameters. A mode down
|
|
190
|
+
a straight lossless guide gives `|S21|≈1`; the overlap is backend-agnostic
|
|
191
|
+
(NumPy/CuPy/JAX) so `|S_ij|²` is differentiable through the adjoint — the PIC
|
|
192
|
+
inverse-design objective. See [`docs/accuracy.rst`](docs/accuracy.rst).
|
|
193
|
+
- **Full-vectorial waveguide mode solver** (`ModeSolver`): a finite-difference
|
|
194
|
+
eigenproblem for the transverse fields on the Yee grid that distinguishes TE
|
|
195
|
+
from TM and captures high-index-contrast boundary effects. Validated against
|
|
196
|
+
the exact symmetric-slab dispersion to better than 0.01% on both TE0 and TM0
|
|
197
|
+
(Zhu & Brown 2002; Fallahkhair et al. 2008).
|
|
198
|
+
- **gdsfactory adapter** (`from_gdsfactory`) that reads a layout
|
|
199
|
+
`Component`, maps its layers onto user-supplied materials, and returns a
|
|
200
|
+
pre-built `Simulation`.
|
|
201
|
+
- **Optional acceleration backends** for time-stepping: a Numba JIT path and
|
|
202
|
+
a CuPy GPU path (`use_gpu=True`). The GPU path uses only generic CuPy array
|
|
203
|
+
ops, so it runs on either an **NVIDIA** GPU (CuPy's CUDA build,
|
|
204
|
+
`cupy-cuda12x`) or an **AMD** GPU (CuPy's ROCm build, `cupy-rocm-5-0`).
|
|
205
|
+
Both are optional; without them the plain NumPy core runs.
|
|
206
|
+
- **Differentiable JAX backend** (`use_jax=True`): a pure-functional
|
|
207
|
+
reimplementation of the Yee + CPML step under `jax.lax.scan`, JIT-compiled
|
|
208
|
+
through XLA (CPU/GPU/TPU from one path) and matching the in-core solver to
|
|
209
|
+
machine precision. Because the stepper is a pure function of the
|
|
210
|
+
permittivity, `pf.jax_value_and_grad_eps(sim, loss)` returns the gradient of
|
|
211
|
+
any monitor-based scalar w.r.t. `eps_r` — a **time-domain adjoint for
|
|
212
|
+
gradient-based inverse design / topology optimization**. See
|
|
213
|
+
`photonfdtd.jaxbackend`. (`pip install "photonfdtd[jax]"`.)
|
|
214
|
+
|
|
215
|
+
### Running on GPU
|
|
216
|
+
|
|
217
|
+
**JAX is the GPU backend.** With `use_jax=True` the forward run and the
|
|
218
|
+
differentiable / reversible adjoints run on the GPU automatically once a CUDA
|
|
219
|
+
`jaxlib` is present — no code change: `pip install "jax[cuda12]"`. Validated on
|
|
220
|
+
an NVIDIA RTX 4080 (CUDA 12): forward matches NumPy to ~1e-16, and both adjoints
|
|
221
|
+
run on-device. If JAX falls back to CPU with a "cuSPARSE not found"-style error,
|
|
222
|
+
the nvidia CUDA libraries just aren't on the loader path — add them, e.g.
|
|
223
|
+
`export LD_LIBRARY_PATH=$(python -c "import nvidia,glob,os;print(':'.join(glob.glob(os.path.dirname(nvidia.__file__)+'/*/lib')))")`.
|
|
224
|
+
Large differentiable runs fit in GPU memory thanks to gradient checkpointing and
|
|
225
|
+
the O(1)-memory reversible adjoint (`photonfdtd.reversible`).
|
|
226
|
+
|
|
227
|
+
The **CuPy backend** (`use_gpu=True`) is a deprecated legacy path — superseded by
|
|
228
|
+
JAX for GPU work, but retained (optional; `pip install cupy-cuda12x`) for
|
|
229
|
+
AMD/ROCm GPUs and for **GPU/host/disk out-of-core** (`use_gpu=True` +
|
|
230
|
+
`run(out_of_core=True)`), where fields live on disk and each tile is processed on
|
|
231
|
+
the GPU so peak device memory is one tile — a volume larger than GPU memory still
|
|
232
|
+
runs (JAX cannot stream to disk).
|
|
233
|
+
|
|
234
|
+
## What v0.2 does *not* do (yet)
|
|
235
|
+
|
|
236
|
+
Items below are intentional out-of-scope and tracked in the issue
|
|
237
|
+
tracker:
|
|
238
|
+
|
|
239
|
+
- Dispersive media (Lorentz / Drude / Debye) - planned next, via the
|
|
240
|
+
auxiliary-differential-equation method.
|
|
241
|
+
- Full-vectorial 2D mode solving with E and H couplings.
|
|
242
|
+
- Anisotropic media.
|
|
243
|
+
- Sub-cell averaging for polygon edges.
|
|
244
|
+
- Total-field / scattered-field plane wave injection.
|
|
245
|
+
|
|
246
|
+
## Roadmap
|
|
247
|
+
|
|
248
|
+
- v0.3: dispersive media (Lorentz pole-residue), full-vectorial mode
|
|
249
|
+
solver, anisotropic materials.
|
|
250
|
+
- v0.4: Numba/JAX backend for ~10-100x speed-up.
|
|
251
|
+
|
|
252
|
+
## License
|
|
253
|
+
|
|
254
|
+
MIT.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# photonfdtd
|
|
2
|
+
|
|
3
|
+
A small, fully-local open-source FDTD + waveguide mode solver written in
|
|
4
|
+
Python/NumPy. It implements a Yee-grid FDTD time-stepper with CPML
|
|
5
|
+
absorbing boundaries, dispersive materials, anisotropic subpixel smoothing,
|
|
6
|
+
and a full-vectorial waveguide mode solver, with an API intentionally similar
|
|
7
|
+
in spirit to Tidy3D.
|
|
8
|
+
|
|
9
|
+
This is alpha-stage software (v0.1). The pieces that exist are tested and
|
|
10
|
+
correct; the pieces that don't, don't. See *Status* below.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install photonfdtd
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
or from source, either directly from GitHub:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install "git+https://github.com/ngpaladi/photonfdtd"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
or from a checkout:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git clone https://github.com/ngpaladi/photonfdtd
|
|
28
|
+
cd photonfdtd
|
|
29
|
+
pip install -e .
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The PyPI wheel is pure Python (NumPy core; `photonfdtd[jax]` for the
|
|
33
|
+
differentiable XLA backend). The optional compiled Rust / CUDA backends
|
|
34
|
+
(`backend="rust"`, `backend="rust-cuda"`) are built separately from the Rust
|
|
35
|
+
sources shipped in the sdist — see [`benchmarks/README.md`](benchmarks/README.md).
|
|
36
|
+
|
|
37
|
+
### Releasing (maintainers)
|
|
38
|
+
|
|
39
|
+
Releases publish to PyPI automatically via
|
|
40
|
+
[`.github/workflows/publish.yml`](.github/workflows/publish.yml) using PyPI
|
|
41
|
+
**Trusted Publishing** (OIDC — no API token stored). To cut a release:
|
|
42
|
+
|
|
43
|
+
1. Bump the version in `pyproject.toml` **and** `src/photonfdtd/__init__.py`
|
|
44
|
+
(kept in sync by `tests/test_version.py`).
|
|
45
|
+
2. Publish a GitHub Release tagged `vX.Y.Z` matching that version. The workflow
|
|
46
|
+
runs the tests, guards tag-vs-version, builds the sdist + wheel, and
|
|
47
|
+
publishes.
|
|
48
|
+
|
|
49
|
+
One-time setup before the first release: on PyPI, add a *trusted publisher* for
|
|
50
|
+
the project (Publishing → add a GitHub Actions publisher) with owner `ngpaladi`,
|
|
51
|
+
repo `photonfdtd`, workflow `publish.yml`, environment `pypi`. Until the project
|
|
52
|
+
exists, use PyPI's *pending publisher* form with the same values.
|
|
53
|
+
|
|
54
|
+
## Quick start
|
|
55
|
+
|
|
56
|
+
### A point dipole radiating in 2D vacuum
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import photonfdtd as pf
|
|
60
|
+
|
|
61
|
+
lam0 = 1.0e-6
|
|
62
|
+
freq0 = pf.C_0 / lam0
|
|
63
|
+
dx = lam0 / 20
|
|
64
|
+
|
|
65
|
+
grid = pf.Grid(size=(4e-6, 4e-6), cell_size=dx, pml_layers=(12, 12, 0))
|
|
66
|
+
src = pf.PointDipole(
|
|
67
|
+
position=(0.0, 0.0),
|
|
68
|
+
component="Ez",
|
|
69
|
+
waveform=pf.GaussianPulse(freq0=freq0, fwhm=10e-15),
|
|
70
|
+
)
|
|
71
|
+
mon = pf.FieldMonitor(name="snap", components=("Ez",), interval=20)
|
|
72
|
+
|
|
73
|
+
sim = pf.Simulation(grid, sources=[src], monitors=[mon], run_time=200e-15)
|
|
74
|
+
result = sim.run()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`result.fields["snap"]["Ez"]` is a `(n_frames, ny, nz)` array of field snapshots.
|
|
78
|
+
|
|
79
|
+
### Solving a slab waveguide mode
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
import photonfdtd as pf
|
|
83
|
+
|
|
84
|
+
clad = pf.Medium.from_index(1.0)
|
|
85
|
+
core = pf.Medium.from_index(2.0)
|
|
86
|
+
slab = pf.Box(center=(0.0, 0.0), size=(0.5e-6, 0.3e-6), medium=core)
|
|
87
|
+
|
|
88
|
+
ms = pf.ModeSolver(
|
|
89
|
+
size=(4e-6, 3e-6),
|
|
90
|
+
cell_size=20e-9,
|
|
91
|
+
structures=[slab],
|
|
92
|
+
wavelength=1.55e-6,
|
|
93
|
+
num_modes=2,
|
|
94
|
+
)
|
|
95
|
+
result = ms.solve()
|
|
96
|
+
print(result.n_eff) # array of effective indices
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## What v0.2 actually does
|
|
100
|
+
|
|
101
|
+
- **Yee-grid FDTD** in 1D / 2D / 3D, with the Courant-stable time step
|
|
102
|
+
selected automatically.
|
|
103
|
+
- **CPML** absorbing boundaries (Roden & Gedney 2000, kappa = 1) on any
|
|
104
|
+
number of axes.
|
|
105
|
+
- **Isotropic dielectric media, non-dispersive or dispersive.** Non-dispersive
|
|
106
|
+
media stamp a single `eps_r` per cell; dispersive media (`DispersiveMedium`,
|
|
107
|
+
with `.lorentz` / `.drude` / `.sellmeier` constructors) carry Lorentz, Drude
|
|
108
|
+
and Sellmeier poles advanced by the auxiliary-differential-equation (ADE)
|
|
109
|
+
method during stepping. A cited material library (`pf.silica()`,
|
|
110
|
+
`pf.silicon()`, `pf.silicon_nitride()`, `pf.lithium_niobate()`, `pf.gold()`,
|
|
111
|
+
`pf.silver()`) reproduces each source's published index — see
|
|
112
|
+
[`docs/material_data.md`](docs/material_data.md).
|
|
113
|
+
- **Anisotropic subpixel smoothing** (`Simulation(subpixel=True)`): partially
|
|
114
|
+
filled interface cells get an effective diagonal permittivity tensor
|
|
115
|
+
(harmonic mean normal to the interface, arithmetic mean tangential), removing
|
|
116
|
+
the staircase error and restoring second-order boundary accuracy
|
|
117
|
+
(Farjadpour et al. 2006). See [`docs/accuracy.rst`](docs/accuracy.rst).
|
|
118
|
+
- **Geometry primitives**: axis-aligned `Box` and arbitrary-polygon
|
|
119
|
+
`PolySlab` (a polygon in xy extruded between two z bounds).
|
|
120
|
+
- **Sources**: soft point-dipole (`PointDipole`), distributed line/area
|
|
121
|
+
mode injection (`ModeSource`), an energy-normalised `SinglePhotonSource`
|
|
122
|
+
whose amplitude is set so the launched wavepacket carries exactly
|
|
123
|
+
$h\,\nu$ of total electromagnetic energy, and a moving-charge
|
|
124
|
+
`ChargedParticle` current source that emits **Cherenkov radiation** when
|
|
125
|
+
it outruns the local phase velocity (see `examples/04_cherenkov.py`).
|
|
126
|
+
- **Monitors**: time-domain field snapshots (`FieldMonitor`), Poynting flux
|
|
127
|
+
(`FluxMonitor`), and a frequency-domain `DFTMonitor` that accumulates a
|
|
128
|
+
running Fourier transform at chosen frequencies so storage scales with the
|
|
129
|
+
number of frequencies rather than the number of timesteps (routinely
|
|
130
|
+
50-1000x smaller than an equivalent time-domain monitor, exact at those
|
|
131
|
+
frequencies).
|
|
132
|
+
- **Memory efficiency for large volumes**: per-array `float32`/`float64`
|
|
133
|
+
precision, the permittivity grid released during stepping, a shared curl
|
|
134
|
+
scratch buffer that avoids per-step full-domain temporaries, and
|
|
135
|
+
`FieldMonitor(compression=...)`, which streams snapshots to disk as
|
|
136
|
+
per-frame-scaled, quantised, compressed blocks - keeping RAM flat regardless
|
|
137
|
+
of recording length and shrinking stored data ~10-30x (8-bit) or ~5-7x
|
|
138
|
+
(16-bit) versus an uncompressed float64 monitor.
|
|
139
|
+
- **Out-of-core stepping** (`sim.run(out_of_core=True, tile_cells=...)`): for a
|
|
140
|
+
volume whose fields do not fit in RAM, the six field arrays, `ce_field` and
|
|
141
|
+
the CPML state are memory-mapped to disk and each timestep sweeps the domain
|
|
142
|
+
in slabs along one axis, so **peak RAM is bounded by the tile size, not the
|
|
143
|
+
grid**. Reproduces the in-core result to machine precision. NumPy backend,
|
|
144
|
+
point sources and `FieldMonitor` (incl. `compression=`) supported; see
|
|
145
|
+
`photonfdtd.outofcore`.
|
|
146
|
+
- **Differentiable PIC inverse design.** A substrate/etched-core/cladding stack
|
|
147
|
+
driven by a 2-D etch density `ρ(x,y)` (`EtchedCore`) maps — conic filter →
|
|
148
|
+
`tanh` projection → anisotropic subpixel sidewalls → 3-D ε — entirely in JAX,
|
|
149
|
+
so `value_and_grad_density` returns `d(loss)/d(ρ)` through the full time
|
|
150
|
+
evolution (topology optimization; gradient matches finite differences to
|
|
151
|
+
~1e-8). Ports are launched one-way with `UniModeSource` (equivalence-principle
|
|
152
|
+
current sheets). See [`docs/accuracy.rst`](docs/accuracy.rst).
|
|
153
|
+
- **Mode-decomposition S-parameters** (`s_parameters`, `mode_amplitudes`):
|
|
154
|
+
project the frequency-domain fields on a memory-light port plane (any axis via
|
|
155
|
+
`DFTMonitor(plane_axis=..., plane_position=...)`) onto a solved mode to get
|
|
156
|
+
complex forward/backward modal amplitudes and hence S-parameters. A mode down
|
|
157
|
+
a straight lossless guide gives `|S21|≈1`; the overlap is backend-agnostic
|
|
158
|
+
(NumPy/CuPy/JAX) so `|S_ij|²` is differentiable through the adjoint — the PIC
|
|
159
|
+
inverse-design objective. See [`docs/accuracy.rst`](docs/accuracy.rst).
|
|
160
|
+
- **Full-vectorial waveguide mode solver** (`ModeSolver`): a finite-difference
|
|
161
|
+
eigenproblem for the transverse fields on the Yee grid that distinguishes TE
|
|
162
|
+
from TM and captures high-index-contrast boundary effects. Validated against
|
|
163
|
+
the exact symmetric-slab dispersion to better than 0.01% on both TE0 and TM0
|
|
164
|
+
(Zhu & Brown 2002; Fallahkhair et al. 2008).
|
|
165
|
+
- **gdsfactory adapter** (`from_gdsfactory`) that reads a layout
|
|
166
|
+
`Component`, maps its layers onto user-supplied materials, and returns a
|
|
167
|
+
pre-built `Simulation`.
|
|
168
|
+
- **Optional acceleration backends** for time-stepping: a Numba JIT path and
|
|
169
|
+
a CuPy GPU path (`use_gpu=True`). The GPU path uses only generic CuPy array
|
|
170
|
+
ops, so it runs on either an **NVIDIA** GPU (CuPy's CUDA build,
|
|
171
|
+
`cupy-cuda12x`) or an **AMD** GPU (CuPy's ROCm build, `cupy-rocm-5-0`).
|
|
172
|
+
Both are optional; without them the plain NumPy core runs.
|
|
173
|
+
- **Differentiable JAX backend** (`use_jax=True`): a pure-functional
|
|
174
|
+
reimplementation of the Yee + CPML step under `jax.lax.scan`, JIT-compiled
|
|
175
|
+
through XLA (CPU/GPU/TPU from one path) and matching the in-core solver to
|
|
176
|
+
machine precision. Because the stepper is a pure function of the
|
|
177
|
+
permittivity, `pf.jax_value_and_grad_eps(sim, loss)` returns the gradient of
|
|
178
|
+
any monitor-based scalar w.r.t. `eps_r` — a **time-domain adjoint for
|
|
179
|
+
gradient-based inverse design / topology optimization**. See
|
|
180
|
+
`photonfdtd.jaxbackend`. (`pip install "photonfdtd[jax]"`.)
|
|
181
|
+
|
|
182
|
+
### Running on GPU
|
|
183
|
+
|
|
184
|
+
**JAX is the GPU backend.** With `use_jax=True` the forward run and the
|
|
185
|
+
differentiable / reversible adjoints run on the GPU automatically once a CUDA
|
|
186
|
+
`jaxlib` is present — no code change: `pip install "jax[cuda12]"`. Validated on
|
|
187
|
+
an NVIDIA RTX 4080 (CUDA 12): forward matches NumPy to ~1e-16, and both adjoints
|
|
188
|
+
run on-device. If JAX falls back to CPU with a "cuSPARSE not found"-style error,
|
|
189
|
+
the nvidia CUDA libraries just aren't on the loader path — add them, e.g.
|
|
190
|
+
`export LD_LIBRARY_PATH=$(python -c "import nvidia,glob,os;print(':'.join(glob.glob(os.path.dirname(nvidia.__file__)+'/*/lib')))")`.
|
|
191
|
+
Large differentiable runs fit in GPU memory thanks to gradient checkpointing and
|
|
192
|
+
the O(1)-memory reversible adjoint (`photonfdtd.reversible`).
|
|
193
|
+
|
|
194
|
+
The **CuPy backend** (`use_gpu=True`) is a deprecated legacy path — superseded by
|
|
195
|
+
JAX for GPU work, but retained (optional; `pip install cupy-cuda12x`) for
|
|
196
|
+
AMD/ROCm GPUs and for **GPU/host/disk out-of-core** (`use_gpu=True` +
|
|
197
|
+
`run(out_of_core=True)`), where fields live on disk and each tile is processed on
|
|
198
|
+
the GPU so peak device memory is one tile — a volume larger than GPU memory still
|
|
199
|
+
runs (JAX cannot stream to disk).
|
|
200
|
+
|
|
201
|
+
## What v0.2 does *not* do (yet)
|
|
202
|
+
|
|
203
|
+
Items below are intentional out-of-scope and tracked in the issue
|
|
204
|
+
tracker:
|
|
205
|
+
|
|
206
|
+
- Dispersive media (Lorentz / Drude / Debye) - planned next, via the
|
|
207
|
+
auxiliary-differential-equation method.
|
|
208
|
+
- Full-vectorial 2D mode solving with E and H couplings.
|
|
209
|
+
- Anisotropic media.
|
|
210
|
+
- Sub-cell averaging for polygon edges.
|
|
211
|
+
- Total-field / scattered-field plane wave injection.
|
|
212
|
+
|
|
213
|
+
## Roadmap
|
|
214
|
+
|
|
215
|
+
- v0.3: dispersive media (Lorentz pole-residue), full-vectorial mode
|
|
216
|
+
solver, anisotropic materials.
|
|
217
|
+
- v0.4: Numba/JAX backend for ~10-100x speed-up.
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
MIT.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Backend benchmarks
|
|
2
|
+
|
|
3
|
+
## Building the Rust backends
|
|
4
|
+
|
|
5
|
+
The experimental `backend="rust"` (CPU, rayon) and `backend="rust-cuda"`
|
|
6
|
+
(GPU, cudarc/NVRTC) stepping cores live in `rust/src/`. Build and install
|
|
7
|
+
into the source tree with:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd rust
|
|
11
|
+
cargo build --release --features cuda # drop --features cuda for CPU-only
|
|
12
|
+
cp target/release/lib_photonfdtd_rs.so ../src/photonfdtd/_photonfdtd_rs.so
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
(`maturin develop --release` works too, if you have it.) The `cuda` feature
|
|
16
|
+
pins `cudarc/cuda-12020` for a CUDA 12.2 driver - adjust to yours. The
|
|
17
|
+
parity suite is `tests/test_rust.py`; CUDA tests skip cleanly without a GPU.
|
|
18
|
+
|
|
19
|
+
## Elliptic (focusing) grating coupler, 2D plan view
|
|
20
|
+
|
|
21
|
+
`bench_grating_coupler.py` runs a focusing grating coupler by the
|
|
22
|
+
effective-index method: a single-mode access waveguide opening into a slab
|
|
23
|
+
wedge crossed by the confocal-ellipse grating-line family
|
|
24
|
+
`q*lam = n_slab*r - n_c*sin(theta)*x`. 0.56M cells (880x640 at 25 nm),
|
|
25
|
+
6295 steps, float64, CPML on all sides.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
python benchmarks/bench_grating_coupler.py rust|jax|numpy [outdir]
|
|
29
|
+
python benchmarks/make_gifs.py <outdir> rust jax # field movies
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Measured 2026-07-23 on a 24-core machine with an RTX 4080 (JAX 0.10.2),
|
|
33
|
+
one backend per process. JAX walls include XLA compile (~1.2 s f64 / ~0.5 s
|
|
34
|
+
f32; there is no persistent compilation cache configured), shown separately
|
|
35
|
+
as "steady":
|
|
36
|
+
|
|
37
|
+
| backend | prec | wall (s) | steady (s) | Mcell-steps/s | peak RSS (MB) |
|
|
38
|
+
|----------------|------|---------:|-----------:|--------------:|--------------:|
|
|
39
|
+
| rust-cuda | f64 | 0.81 | 0.81 | 4380 | 385 |
|
|
40
|
+
| rust-cuda | f32 | 0.57 | 0.57 | 6185 | 380 |
|
|
41
|
+
| jax (GPU) | f64 | 3.8 | ~2.6 | 928 | 1248 |
|
|
42
|
+
| jax (GPU) | f32 | 1.9 | ~1.4 | 1894 | 1068 |
|
|
43
|
+
| rust (CPU) | f64 | 8.9 | 8.9 | 397 | 258 |
|
|
44
|
+
| rust (CPU) | f32 | 6.7 | 6.7 | 529 | 239 |
|
|
45
|
+
| jax (CPU) | f64 | 15.6 | 15.6 | 227 | 858 |
|
|
46
|
+
| numpy | f64 | 147.9 | 147.9 | 24 | 281 |
|
|
47
|
+
|
|
48
|
+
All f64 backends agree on the final field frame to ~2e-14 relative
|
|
49
|
+
(double-precision round-off over 6295 steps); f32 runs agree to ~5e-6, i.e.
|
|
50
|
+
single-precision round-off. The Rust CUDA margin over XLA comes from fused
|
|
51
|
+
H/E kernels (each array is touched once per pass) and in-place updates;
|
|
52
|
+
both saturate the same VRAM bandwidth ceiling in principle. (This small
|
|
53
|
+
0.56M-cell domain is largely cache-resident on the CPU, so CPU numbers here
|
|
54
|
+
are compute-bound; the RAM-bound regime below is what large runs see.)
|
|
55
|
+
|
|
56
|
+
## RAM-bound CPU throughput (20M-cell strip, whole-chip regime)
|
|
57
|
+
|
|
58
|
+
Same strip as the memory benchmark. The fused H/E passes and ghost-zone
|
|
59
|
+
temporal blocking (x-slab tiles advanced T=8 steps per visit inside a
|
|
60
|
+
~3.5 MB thread-local buffer; enabled automatically on large domains, exact
|
|
61
|
+
to the bit) both target this regime:
|
|
62
|
+
|
|
63
|
+
| CPU path | f64 Mcell-steps/s | f32 Mcell-steps/s |
|
|
64
|
+
|-------------------------------|------------------:|------------------:|
|
|
65
|
+
| unfused (previous) | 248 | - |
|
|
66
|
+
| fused, plain | 310 | 626 |
|
|
67
|
+
| fused + temporal blocking | 382 | 913 |
|
|
68
|
+
|
|
69
|
+
For scale, the measured STREAM triad on this machine is 34.3 GB/s; the
|
|
70
|
+
fused plain path already runs at that roof, and blocking is what buys
|
|
71
|
+
throughput past it.
|
|
72
|
+
|
|
73
|
+
## Beyond-VRAM GPU streaming
|
|
74
|
+
|
|
75
|
+
When the stepping state does not fit the device (or with
|
|
76
|
+
`PHOTONFDTD_CUDA_STREAM=1`), `rust-cuda` keeps the domain in host RAM and
|
|
77
|
+
streams temporally-blocked x-slab tiles through the GPU over the DMA copy
|
|
78
|
+
engines - per T steps the domain crosses PCIe ~twice instead of 2T times.
|
|
79
|
+
Bit-identical to the resident stepper (covered by the parity tests). On the
|
|
80
|
+
20M-cell f64 strip, forced streaming with a ~1/5-domain tile (T=64):
|
|
81
|
+
|
|
82
|
+
| GPU path | f64 Mcell-steps/s | f32 Mcell-steps/s | peak VRAM (MB) |
|
|
83
|
+
|---------------------------|------------------:|------------------:|---------------:|
|
|
84
|
+
| VRAM-resident | 4067 | 7835 | 1331 |
|
|
85
|
+
| streamed, tile-bounded | 2483 | 5243 | 499 |
|
|
86
|
+
|
|
87
|
+
Streaming holds ~60% of resident throughput while VRAM usage is set by the
|
|
88
|
+
tile, not the domain - so domain size is limited by host RAM (~800M cells
|
|
89
|
+
f32 on a 30 GB host, stepped ~13x faster than the best CPU path). Naive
|
|
90
|
+
per-step streaming would be PCIe-bound at roughly 1% of this.
|
|
91
|
+
|
|
92
|
+
## Peak memory at whole-chip scale
|
|
93
|
+
|
|
94
|
+
`bench_memory.py` steps a 0.5 mm x 25 um strip at 25 nm (20M cells, 2D,
|
|
95
|
+
float64) a few dozen steps and reports peak memory (VRAM polled with
|
|
96
|
+
nvidia-smi):
|
|
97
|
+
|
|
98
|
+
| backend | peak RSS (GB) | peak VRAM (GB) |
|
|
99
|
+
|-----------|--------------:|---------------:|
|
|
100
|
+
| rust-cuda | 0.51 | 1.33 |
|
|
101
|
+
| rust | 1.27 | - |
|
|
102
|
+
| numpy | 1.72 | - |
|
|
103
|
+
| jax (GPU) | 1.65 | 3.33 |
|
|
104
|
+
|
|
105
|
+
Both Rust cores store their CPML psi state compacted to the PML slabs, so
|
|
106
|
+
the resident set is essentially the floor for an in-core run: six field
|
|
107
|
+
arrays + the update coefficient (7 x 8 bytes/cell ~ 1.19 GB here; XLA's
|
|
108
|
+
dense psi carries ~19 arrays/cell). Extrapolating, a 16 GB GPU holds a
|
|
109
|
+
~230M-cell f64 domain (~460M cells at f32, e.g. ~11 x 1 mm of 2D chip at
|
|
110
|
+
25 nm) on `rust-cuda`, vs ~90M cells for JAX-GPU; a 30 GB host holds
|
|
111
|
+
~500M cells f64 on the CPU core. Beyond that, `run(out_of_core=True)`
|
|
112
|
+
remains the escape hatch.
|
|
113
|
+
|
|
114
|
+
## Notes: GPU environment
|
|
115
|
+
|
|
116
|
+
The JAX CUDA plugin needs the `nvidia-*-cu12` wheels' lib dirs on
|
|
117
|
+
`LD_LIBRARY_PATH` on this machine (installed via
|
|
118
|
+
`pip install --user "jax[cuda12]"`). `rust-cuda` needs only the CUDA driver
|
|
119
|
+
plus NVRTC (from the system toolkit or the `nvidia-cuda-nvrtc-cu12` wheel).
|
|
120
|
+
|
|
121
|
+
## Tuning knobs (env vars)
|
|
122
|
+
|
|
123
|
+
All optional; the defaults come from the scans above.
|
|
124
|
+
|
|
125
|
+
- `PHOTONFDTD_RUST_TB` - CPU temporal-block length T (0 disables; default 8
|
|
126
|
+
on large domains).
|
|
127
|
+
- `PHOTONFDTD_RUST_TILE` - CPU tile core rows (default ~3.5 MB buffers).
|
|
128
|
+
- `PHOTONFDTD_CUDA_STREAM` - 1 forces GPU streaming, 0 forces resident
|
|
129
|
+
(default: stream only when the run does not fit VRAM).
|
|
130
|
+
- `PHOTONFDTD_CUDA_TB` / `PHOTONFDTD_CUDA_TILE` - streaming block length
|
|
131
|
+
(default 32) and tile rows (default ~1/4 of free VRAM).
|
|
132
|
+
|
|
133
|
+
Streaming transfers are currently pageable; pinned-host-memory double
|
|
134
|
+
buffering would overlap DMA with compute and should recover most of the
|
|
135
|
+
remaining gap to resident throughput.
|