witwin-maxwell 0.3.0__py3-none-win_amd64.whl
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.
- witwin/maxwell/__init__.py +164 -0
- witwin/maxwell/adapters/__init__.py +1 -0
- witwin/maxwell/adapters/gds.py +203 -0
- witwin/maxwell/adapters/tidy3d.py +1669 -0
- witwin/maxwell/adjoint_inputs.py +70 -0
- witwin/maxwell/compiler/__init__.py +11 -0
- witwin/maxwell/compiler/materials.py +1355 -0
- witwin/maxwell/compiler/monitors.py +164 -0
- witwin/maxwell/compiler/sources.py +222 -0
- witwin/maxwell/fdfd/__init__.py +3 -0
- witwin/maxwell/fdfd/adjoint/__init__.py +3 -0
- witwin/maxwell/fdfd/adjoint/bridge.py +180 -0
- witwin/maxwell/fdfd/ams.py +300 -0
- witwin/maxwell/fdfd/krylov.py +280 -0
- witwin/maxwell/fdfd/plotting.py +110 -0
- witwin/maxwell/fdfd/postprocess.py +33 -0
- witwin/maxwell/fdfd/solver.py +1057 -0
- witwin/maxwell/fdtd/__init__.py +4 -0
- witwin/maxwell/fdtd/adjoint/__init__.py +21 -0
- witwin/maxwell/fdtd/adjoint/bridge.py +746 -0
- witwin/maxwell/fdtd/adjoint/capabilities.py +29 -0
- witwin/maxwell/fdtd/adjoint/core.py +3614 -0
- witwin/maxwell/fdtd/adjoint/dispatch.py +684 -0
- witwin/maxwell/fdtd/adjoint/native.py +2561 -0
- witwin/maxwell/fdtd/adjoint/profiler.py +195 -0
- witwin/maxwell/fdtd/adjoint/reverse_common.py +207 -0
- witwin/maxwell/fdtd/adjoint/seeds.py +524 -0
- witwin/maxwell/fdtd/boundary/__init__.py +37 -0
- witwin/maxwell/fdtd/boundary/bloch.py +90 -0
- witwin/maxwell/fdtd/boundary/common.py +41 -0
- witwin/maxwell/fdtd/boundary/cpml.py +527 -0
- witwin/maxwell/fdtd/boundary/runtime.py +209 -0
- witwin/maxwell/fdtd/checkpoint.py +227 -0
- witwin/maxwell/fdtd/coords.py +32 -0
- witwin/maxwell/fdtd/cuda/__init__.py +11 -0
- witwin/maxwell/fdtd/cuda/backend.py +3532 -0
- witwin/maxwell/fdtd/cuda/build.py +292 -0
- witwin/maxwell/fdtd/cuda/extension.cpp +2053 -0
- witwin/maxwell/fdtd/cuda/kernels/adjoint.cu +5290 -0
- witwin/maxwell/fdtd/cuda/kernels/aniso.cu +1704 -0
- witwin/maxwell/fdtd/cuda/kernels/boundary.cu +424 -0
- witwin/maxwell/fdtd/cuda/kernels/common.cu +74 -0
- witwin/maxwell/fdtd/cuda/kernels/common.cuh +82 -0
- witwin/maxwell/fdtd/cuda/kernels/dispersive.cu +782 -0
- witwin/maxwell/fdtd/cuda/kernels/electric.cu +3613 -0
- witwin/maxwell/fdtd/cuda/kernels/magnetic.cu +781 -0
- witwin/maxwell/fdtd/cuda/kernels/observers.cu +347 -0
- witwin/maxwell/fdtd/cuda/kernels/projection.cu +211 -0
- witwin/maxwell/fdtd/cuda/kernels/sources.cu +1482 -0
- witwin/maxwell/fdtd/cuda/kernels/spectral.cu +297 -0
- witwin/maxwell/fdtd/cuda/launch.h +23 -0
- witwin/maxwell/fdtd/cuda/prebuilt/witwin_maxwell_fdtd_cuda.pyd +0 -0
- witwin/maxwell/fdtd/cuda/runtime/__init__.py +6 -0
- witwin/maxwell/fdtd/cuda/runtime/dispatcher.py +6 -0
- witwin/maxwell/fdtd/cuda/runtime/graph.py +33 -0
- witwin/maxwell/fdtd/cuda/tensors.h +35 -0
- witwin/maxwell/fdtd/dispersion.py +48 -0
- witwin/maxwell/fdtd/evaluation.py +290 -0
- witwin/maxwell/fdtd/excitation/__init__.py +27 -0
- witwin/maxwell/fdtd/excitation/injection.py +1102 -0
- witwin/maxwell/fdtd/excitation/modes.py +1675 -0
- witwin/maxwell/fdtd/excitation/spatial.py +469 -0
- witwin/maxwell/fdtd/excitation/temporal.py +510 -0
- witwin/maxwell/fdtd/excitation/tfsf_apply.py +264 -0
- witwin/maxwell/fdtd/excitation/tfsf_common.py +421 -0
- witwin/maxwell/fdtd/excitation/tfsf_specs.py +490 -0
- witwin/maxwell/fdtd/excitation/tfsf_state.py +778 -0
- witwin/maxwell/fdtd/io.py +31 -0
- witwin/maxwell/fdtd/material_pullback.py +371 -0
- witwin/maxwell/fdtd/meshing/__init__.py +3 -0
- witwin/maxwell/fdtd/meshing/autogrid.py +335 -0
- witwin/maxwell/fdtd/observers.py +1453 -0
- witwin/maxwell/fdtd/plotting.py +121 -0
- witwin/maxwell/fdtd/postprocess.py +205 -0
- witwin/maxwell/fdtd/runtime/__init__.py +119 -0
- witwin/maxwell/fdtd/runtime/initialization.py +236 -0
- witwin/maxwell/fdtd/runtime/materials.py +1563 -0
- witwin/maxwell/fdtd/runtime/module_cache.py +31 -0
- witwin/maxwell/fdtd/runtime/spectral.py +581 -0
- witwin/maxwell/fdtd/runtime/stepping.py +1987 -0
- witwin/maxwell/fdtd/solver.py +502 -0
- witwin/maxwell/geometry/__init__.py +5 -0
- witwin/maxwell/geometry/polyslab.py +299 -0
- witwin/maxwell/materials.py +3 -0
- witwin/maxwell/media.py +1467 -0
- witwin/maxwell/monitors.py +743 -0
- witwin/maxwell/ports.py +113 -0
- witwin/maxwell/postprocess/RCS.py +139 -0
- witwin/maxwell/postprocess/__init__.py +42 -0
- witwin/maxwell/postprocess/diffraction.py +219 -0
- witwin/maxwell/postprocess/directivity.py +152 -0
- witwin/maxwell/postprocess/emission.py +67 -0
- witwin/maxwell/postprocess/modal.py +324 -0
- witwin/maxwell/postprocess/nfft.py +266 -0
- witwin/maxwell/postprocess/scattering_parameters.py +358 -0
- witwin/maxwell/postprocess/stratton_chu.py +1193 -0
- witwin/maxwell/result.py +967 -0
- witwin/maxwell/scene.py +1338 -0
- witwin/maxwell/simulation.py +659 -0
- witwin/maxwell/sources.py +917 -0
- witwin/maxwell/visualization/__init__.py +28 -0
- witwin/maxwell/visualization/interactive.py +484 -0
- witwin/maxwell/visualization/plots.py +568 -0
- witwin_maxwell-0.3.0.dist-info/METADATA +184 -0
- witwin_maxwell-0.3.0.dist-info/RECORD +107 -0
- witwin_maxwell-0.3.0.dist-info/WHEEL +4 -0
- witwin_maxwell-0.3.0.dist-info/licenses/LICENSE +13 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from enum import Enum, auto
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
from ..boundary import (
|
|
8
|
+
BOUNDARY_BLOCH,
|
|
9
|
+
BOUNDARY_NONE,
|
|
10
|
+
BOUNDARY_PERIODIC,
|
|
11
|
+
BOUNDARY_PMC,
|
|
12
|
+
has_complex_fields,
|
|
13
|
+
)
|
|
14
|
+
from ..checkpoint import checkpoint_schema
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _ReverseBackend(Enum):
|
|
18
|
+
GENERAL_NONLINEAR = auto()
|
|
19
|
+
MIXED_BLOCH_CPML = auto()
|
|
20
|
+
GRATING_TFSF = auto()
|
|
21
|
+
TFSF = auto()
|
|
22
|
+
BLOCH_DISPERSIVE = auto()
|
|
23
|
+
BLOCH = auto()
|
|
24
|
+
DISPERSIVE = auto()
|
|
25
|
+
STANDARD = auto()
|
|
26
|
+
CPML = auto()
|
|
27
|
+
CONDUCTIVE = auto()
|
|
28
|
+
KERR = auto()
|
|
29
|
+
FULL_ANISO = auto()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Native CUDA reverse-execution backends mirror the native CUDA
|
|
33
|
+
# variants one-for-one. Each label is the value recorded into
|
|
34
|
+
# ``_BackwardProfiler.reverse_backend_counts`` so a native reverse step stays
|
|
35
|
+
# attributable per configuration (``native_standard`` mirrors
|
|
36
|
+
# ``native_standard`` and so on). The runner registry is populated by
|
|
37
|
+
# later P6 items as the fused CUDA reverse kernels land; until an entry exists
|
|
38
|
+
# ``auto`` mode never selects native and an explicit ``native`` override raises.
|
|
39
|
+
_NATIVE_REVERSE_LABELS: dict[_ReverseBackend, str] = {
|
|
40
|
+
_ReverseBackend.GENERAL_NONLINEAR: "native_general_nonlinear",
|
|
41
|
+
_ReverseBackend.MIXED_BLOCH_CPML: "native_mixed_bloch_cpml",
|
|
42
|
+
_ReverseBackend.STANDARD: "native_standard",
|
|
43
|
+
_ReverseBackend.CPML: "native_cpml",
|
|
44
|
+
_ReverseBackend.CONDUCTIVE: "native_conductive",
|
|
45
|
+
_ReverseBackend.KERR: "native_kerr",
|
|
46
|
+
_ReverseBackend.FULL_ANISO: "native_full_aniso",
|
|
47
|
+
_ReverseBackend.BLOCH: "native_bloch",
|
|
48
|
+
_ReverseBackend.BLOCH_DISPERSIVE: "native_bloch_dispersive",
|
|
49
|
+
_ReverseBackend.DISPERSIVE: "native_dispersive",
|
|
50
|
+
_ReverseBackend.TFSF: "native_tfsf",
|
|
51
|
+
_ReverseBackend.GRATING_TFSF: "native_grating_tfsf",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# A registered runner takes the same (solver, forward_state, adjoint_state,
|
|
55
|
+
# time_value, eps_*, resolved_source_terms, profiler) contract as the analytic
|
|
56
|
+
# reference backends and returns a fully-formed ``_ReverseStepResult`` whose
|
|
57
|
+
# ``backend`` field is the matching ``_NATIVE_REVERSE_LABELS`` value.
|
|
58
|
+
_NativeReverseRunner = Callable[..., object]
|
|
59
|
+
_NATIVE_REVERSE_RUNNERS: dict[_ReverseBackend, _NativeReverseRunner] = {}
|
|
60
|
+
|
|
61
|
+
# A runner may carry a ``(solver, forward_state) -> bool`` qualifier stashed on
|
|
62
|
+
# the callable itself (``_native_reverse_qualifier``). The real native runners
|
|
63
|
+
# require a CUDA scene with the compiled extension present, so on CPU or a
|
|
64
|
+
# missing-extension host ``auto`` mode falls back to the native implementation and a
|
|
65
|
+
# forced ``native`` override raises. The qualifier travels with the runner so a
|
|
66
|
+
# test that swaps in its own runner (no qualifier attribute) qualifies anywhere.
|
|
67
|
+
_NATIVE_QUALIFIER_ATTR = "_native_reverse_qualifier"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def register_native_reverse_backend(
|
|
71
|
+
backend: _ReverseBackend,
|
|
72
|
+
runner: _NativeReverseRunner,
|
|
73
|
+
*,
|
|
74
|
+
qualifier: Callable[..., bool] | None = None,
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Register a native CUDA reverse-step runner for an analytic backend variant."""
|
|
77
|
+
if backend not in _NATIVE_REVERSE_LABELS:
|
|
78
|
+
raise ValueError(f"No native reverse label is defined for backend {backend!r}.")
|
|
79
|
+
if qualifier is not None:
|
|
80
|
+
setattr(runner, _NATIVE_QUALIFIER_ATTR, qualifier)
|
|
81
|
+
_NATIVE_REVERSE_RUNNERS[backend] = runner
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def unregister_native_reverse_backend(backend: _ReverseBackend) -> None:
|
|
85
|
+
"""Drop a previously registered native runner (primarily for tests)."""
|
|
86
|
+
_NATIVE_REVERSE_RUNNERS.pop(backend, None)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _native_backend_available(backend: _ReverseBackend, solver=None, forward_state=None) -> bool:
|
|
90
|
+
runner = _NATIVE_REVERSE_RUNNERS.get(backend)
|
|
91
|
+
if runner is None:
|
|
92
|
+
return False
|
|
93
|
+
qualifier = getattr(runner, _NATIVE_QUALIFIER_ATTR, None)
|
|
94
|
+
if qualifier is None:
|
|
95
|
+
return True
|
|
96
|
+
if solver is None:
|
|
97
|
+
# No scene context to evaluate against: report registry membership only.
|
|
98
|
+
return True
|
|
99
|
+
return bool(qualifier(solver, forward_state))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _runtime():
|
|
103
|
+
from . import core as _adjoint
|
|
104
|
+
|
|
105
|
+
return _adjoint
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _face_codes(solver) -> tuple[int, int, int, int, int, int]:
|
|
109
|
+
return (
|
|
110
|
+
int(getattr(solver, "boundary_x_low_code", BOUNDARY_NONE)),
|
|
111
|
+
int(getattr(solver, "boundary_x_high_code", BOUNDARY_NONE)),
|
|
112
|
+
int(getattr(solver, "boundary_y_low_code", BOUNDARY_NONE)),
|
|
113
|
+
int(getattr(solver, "boundary_y_high_code", BOUNDARY_NONE)),
|
|
114
|
+
int(getattr(solver, "boundary_z_low_code", BOUNDARY_NONE)),
|
|
115
|
+
int(getattr(solver, "boundary_z_high_code", BOUNDARY_NONE)),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _matches_checkpoint_layout(solver, forward_state) -> bool:
|
|
120
|
+
return tuple(forward_state.keys()) == checkpoint_schema(solver).state_names
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _has_open_face_conflicts(face_codes: tuple[int, int, int, int, int, int]) -> bool:
|
|
124
|
+
return any(code in {BOUNDARY_PERIODIC, BOUNDARY_PMC, BOUNDARY_BLOCH} for code in face_codes)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _supports_explicit_source_step(runtime, solver, resolved_source_terms) -> bool:
|
|
128
|
+
return runtime._can_use_explicit_source_term_reverse_step(solver, resolved_source_terms)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _supports_tfsf(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
132
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
133
|
+
return False
|
|
134
|
+
if getattr(solver, "conductive_enabled", False):
|
|
135
|
+
return False
|
|
136
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
137
|
+
return False
|
|
138
|
+
if not getattr(solver, "tfsf_enabled", False):
|
|
139
|
+
return False
|
|
140
|
+
if has_complex_fields(solver):
|
|
141
|
+
return False
|
|
142
|
+
if getattr(solver, "dispersive_enabled", False):
|
|
143
|
+
return False
|
|
144
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
145
|
+
return False
|
|
146
|
+
if _has_open_face_conflicts(_face_codes(solver)):
|
|
147
|
+
return False
|
|
148
|
+
if runtime._has_resolved_source_terms(resolved_source_terms):
|
|
149
|
+
return False
|
|
150
|
+
|
|
151
|
+
tfsf_state = getattr(solver, "_tfsf_state", None)
|
|
152
|
+
if tfsf_state is None:
|
|
153
|
+
return False
|
|
154
|
+
provider = tfsf_state.get("provider")
|
|
155
|
+
if provider not in {
|
|
156
|
+
"plane_wave_ref_x_ez",
|
|
157
|
+
"plane_wave_axis_aligned",
|
|
158
|
+
"plane_wave_aux",
|
|
159
|
+
"plane_wave_discrete_cw",
|
|
160
|
+
"analytic_profile",
|
|
161
|
+
}:
|
|
162
|
+
return False
|
|
163
|
+
|
|
164
|
+
auxiliary_grid = tfsf_state.get("auxiliary_grid")
|
|
165
|
+
has_auxiliary_state = "tfsf_aux_electric" in forward_state and "tfsf_aux_magnetic" in forward_state
|
|
166
|
+
if auxiliary_grid is None:
|
|
167
|
+
return not has_auxiliary_state
|
|
168
|
+
return has_auxiliary_state
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _supports_grating_tfsf(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
172
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
173
|
+
return False
|
|
174
|
+
if getattr(solver, "conductive_enabled", False):
|
|
175
|
+
return False
|
|
176
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
177
|
+
return False
|
|
178
|
+
if not getattr(solver, "tfsf_enabled", False):
|
|
179
|
+
return False
|
|
180
|
+
if not has_complex_fields(solver):
|
|
181
|
+
return False
|
|
182
|
+
if not getattr(solver, "uses_cpml", False):
|
|
183
|
+
return False
|
|
184
|
+
if getattr(solver, "dispersive_enabled", False):
|
|
185
|
+
return False
|
|
186
|
+
if runtime._has_resolved_source_terms(resolved_source_terms):
|
|
187
|
+
return False
|
|
188
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
# A grating slab is a single absorbing (PML) normal axis with the two
|
|
192
|
+
# transverse axes periodic (Bloch), for any choice of normal axis. The reverse
|
|
193
|
+
# split-field replay carries the wrap phase on the two Bloch axes and the
|
|
194
|
+
# recursive-convolution stretch on the single PML axis; ``_bloch_cpml_pml_axis``
|
|
195
|
+
# recognizes exactly that layout and returns the absorbing axis.
|
|
196
|
+
from ..runtime.stepping import _bloch_cpml_pml_axis
|
|
197
|
+
|
|
198
|
+
pml_axis = _bloch_cpml_pml_axis(solver)
|
|
199
|
+
if pml_axis is None:
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
tfsf_state = getattr(solver, "_tfsf_state", None)
|
|
203
|
+
if tfsf_state is None:
|
|
204
|
+
return False
|
|
205
|
+
return (
|
|
206
|
+
tfsf_state.get("provider") == "plane_wave_grating_slab_cw"
|
|
207
|
+
and tfsf_state.get("mode") == "slab"
|
|
208
|
+
and tfsf_state.get("axis") == pml_axis
|
|
209
|
+
and "tfsf_aux_electric" not in forward_state
|
|
210
|
+
and "tfsf_aux_magnetic" not in forward_state
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _supports_mixed_bloch_cpml(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
215
|
+
if not has_complex_fields(solver) or not getattr(solver, "uses_cpml", False):
|
|
216
|
+
return False
|
|
217
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
218
|
+
return False
|
|
219
|
+
if getattr(solver, "nonlinear_enabled", False) or getattr(solver, "conductive_enabled", False):
|
|
220
|
+
return False
|
|
221
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
222
|
+
return False
|
|
223
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
224
|
+
return False
|
|
225
|
+
from ..runtime.stepping import _bloch_cpml_pml_axis
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
_bloch_cpml_pml_axis(solver) is not None
|
|
229
|
+
and _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _supports_standard(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
234
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
235
|
+
return False
|
|
236
|
+
if getattr(solver, "conductive_enabled", False):
|
|
237
|
+
return False
|
|
238
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
239
|
+
return False
|
|
240
|
+
if tuple(forward_state.keys()) != ("Ex", "Ey", "Ez", "Hx", "Hy", "Hz"):
|
|
241
|
+
return False
|
|
242
|
+
if getattr(solver, "uses_cpml", False):
|
|
243
|
+
return False
|
|
244
|
+
if has_complex_fields(solver):
|
|
245
|
+
return False
|
|
246
|
+
if getattr(solver, "dispersive_enabled", False):
|
|
247
|
+
return False
|
|
248
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
249
|
+
return False
|
|
250
|
+
if _has_open_face_conflicts(_face_codes(solver)):
|
|
251
|
+
return False
|
|
252
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def _with_profile_sections(profiler, fn):
|
|
256
|
+
if profiler is None:
|
|
257
|
+
return fn()
|
|
258
|
+
with profiler.section("state_clone"):
|
|
259
|
+
pass
|
|
260
|
+
with profiler.section("step_forward"):
|
|
261
|
+
result = fn()
|
|
262
|
+
with profiler.section("step_vjp"):
|
|
263
|
+
pass
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _supports_cpml(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
268
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
269
|
+
return False
|
|
270
|
+
if getattr(solver, "conductive_enabled", False):
|
|
271
|
+
return False
|
|
272
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
273
|
+
return False
|
|
274
|
+
if not getattr(solver, "uses_cpml", False):
|
|
275
|
+
return False
|
|
276
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
277
|
+
return False
|
|
278
|
+
if has_complex_fields(solver):
|
|
279
|
+
return False
|
|
280
|
+
if getattr(solver, "dispersive_enabled", False):
|
|
281
|
+
return False
|
|
282
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
283
|
+
return False
|
|
284
|
+
if _has_open_face_conflicts(_face_codes(solver)):
|
|
285
|
+
return False
|
|
286
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _supports_dispersive(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
290
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
291
|
+
return False
|
|
292
|
+
if getattr(solver, "conductive_enabled", False):
|
|
293
|
+
return False
|
|
294
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
295
|
+
return False
|
|
296
|
+
if not getattr(solver, "dispersive_enabled", False):
|
|
297
|
+
return False
|
|
298
|
+
if has_complex_fields(solver):
|
|
299
|
+
return False
|
|
300
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
301
|
+
return False
|
|
302
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
303
|
+
return False
|
|
304
|
+
if _has_open_face_conflicts(_face_codes(solver)):
|
|
305
|
+
return False
|
|
306
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _supports_bloch(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
310
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
311
|
+
return False
|
|
312
|
+
if getattr(solver, "conductive_enabled", False):
|
|
313
|
+
return False
|
|
314
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
315
|
+
return False
|
|
316
|
+
if getattr(solver, "uses_cpml", False):
|
|
317
|
+
return False
|
|
318
|
+
if not has_complex_fields(solver):
|
|
319
|
+
return False
|
|
320
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
321
|
+
return False
|
|
322
|
+
if getattr(solver, "dispersive_enabled", False):
|
|
323
|
+
return False
|
|
324
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
325
|
+
return False
|
|
326
|
+
if not all(code == BOUNDARY_BLOCH for code in _face_codes(solver)):
|
|
327
|
+
return False
|
|
328
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _supports_bloch_dispersive(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
332
|
+
"""Analytic native reverse for a Bloch (complex-field) + electric-dispersive medium.
|
|
333
|
+
|
|
334
|
+
The complex-field solver advances a real and an imaginary FDTD copy that couple
|
|
335
|
+
only through the Bloch boundary wrap, and the electric ADE poles advance on each
|
|
336
|
+
copy with the same real coefficients. The reverse is the complex Bloch base
|
|
337
|
+
reverse plus the electric-dispersive correction / ADE-state VJP applied to both
|
|
338
|
+
halves, so this variant carries the imaginary-ADE replica the plain Bloch and
|
|
339
|
+
plain dispersive backends each drop. Gated to the pure Bloch (all-faces-Bloch,
|
|
340
|
+
non-CPML) electric-dispersive class; combined with an absorbing (CPML) axis,
|
|
341
|
+
conduction, nonlinearity, full anisotropy, or TFSF combinations are handled
|
|
342
|
+
by another native specialization or rejected during preparation."""
|
|
343
|
+
if not has_complex_fields(solver):
|
|
344
|
+
return False
|
|
345
|
+
if not getattr(solver, "dispersive_enabled", False):
|
|
346
|
+
return False
|
|
347
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
348
|
+
return False
|
|
349
|
+
if getattr(solver, "conductive_enabled", False):
|
|
350
|
+
return False
|
|
351
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
352
|
+
return False
|
|
353
|
+
if getattr(solver, "uses_cpml", False):
|
|
354
|
+
return False
|
|
355
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
356
|
+
return False
|
|
357
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
358
|
+
return False
|
|
359
|
+
if not all(code == BOUNDARY_BLOCH for code in _face_codes(solver)):
|
|
360
|
+
return False
|
|
361
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def _supports_conductive(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
365
|
+
"""Analytic native reverse for a static-conductive (sigma_e) CPML medium.
|
|
366
|
+
|
|
367
|
+
The semi-implicit conduction loss makes the electric ``decay`` and ``curl``
|
|
368
|
+
coefficients eps-dependent, so this variant carries the extra eps sensitivity
|
|
369
|
+
the linear CPML reverse drops. Gated to the pure real, single-material
|
|
370
|
+
conductive class on an absorbing (CPML) grid; conduction combined with an
|
|
371
|
+
open boundary, ADE dispersion, nonlinearity, full anisotropy, complex fields,
|
|
372
|
+
or TFSF is rejected during preparation when no native specialization exists."""
|
|
373
|
+
if not getattr(solver, "conductive_enabled", False):
|
|
374
|
+
return False
|
|
375
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
376
|
+
return False
|
|
377
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
378
|
+
return False
|
|
379
|
+
if not getattr(solver, "uses_cpml", False):
|
|
380
|
+
return False
|
|
381
|
+
if getattr(solver, "dispersive_enabled", False):
|
|
382
|
+
return False
|
|
383
|
+
if getattr(solver, "magnetic_dispersive_enabled", False):
|
|
384
|
+
return False
|
|
385
|
+
if has_complex_fields(solver):
|
|
386
|
+
return False
|
|
387
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
388
|
+
return False
|
|
389
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
390
|
+
return False
|
|
391
|
+
if _has_open_face_conflicts(_face_codes(solver)):
|
|
392
|
+
return False
|
|
393
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _supports_kerr(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
397
|
+
"""Analytic native reverse for an instantaneous Kerr (chi3) CPML medium.
|
|
398
|
+
|
|
399
|
+
The Kerr forward recomputes only the electric ``curl`` coefficient each step
|
|
400
|
+
from the pre-update fields (``curl = (dt / eff) * decay`` with
|
|
401
|
+
``eff = eps + eps0 * chi3 * |E|^2``), so the reverse carries the extra field /
|
|
402
|
+
chi3 sensitivity the linear CPML reverse drops. Gated to the pure real,
|
|
403
|
+
curl-only Kerr class on an absorbing (CPML) grid; the general nonlinear kernel
|
|
404
|
+
(chi2 / two-photon absorption, which also rewrites ``decay``) uses the general
|
|
405
|
+
nonlinear native variant. Unsupported combinations fail during preparation."""
|
|
406
|
+
if not getattr(solver, "kerr_enabled", False):
|
|
407
|
+
return False
|
|
408
|
+
if getattr(solver, "nonlinear_general_enabled", False):
|
|
409
|
+
return False
|
|
410
|
+
if getattr(solver, "conductive_enabled", False):
|
|
411
|
+
return False
|
|
412
|
+
if getattr(solver, "full_aniso_enabled", False):
|
|
413
|
+
return False
|
|
414
|
+
if not getattr(solver, "uses_cpml", False):
|
|
415
|
+
return False
|
|
416
|
+
if getattr(solver, "dispersive_enabled", False):
|
|
417
|
+
return False
|
|
418
|
+
if getattr(solver, "magnetic_dispersive_enabled", False):
|
|
419
|
+
return False
|
|
420
|
+
if has_complex_fields(solver):
|
|
421
|
+
return False
|
|
422
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
423
|
+
return False
|
|
424
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
425
|
+
return False
|
|
426
|
+
if _has_open_face_conflicts(_face_codes(solver)):
|
|
427
|
+
return False
|
|
428
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def _supports_general_nonlinear(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
432
|
+
if not getattr(solver, "nonlinear_general_enabled", False):
|
|
433
|
+
return False
|
|
434
|
+
if has_complex_fields(solver):
|
|
435
|
+
return False
|
|
436
|
+
if getattr(solver, "full_aniso_enabled", False) or getattr(solver, "tfsf_enabled", False):
|
|
437
|
+
return False
|
|
438
|
+
if getattr(solver, "magnetic_dispersive_enabled", False):
|
|
439
|
+
return False
|
|
440
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
441
|
+
return False
|
|
442
|
+
if _has_open_face_conflicts(_face_codes(solver)):
|
|
443
|
+
return False
|
|
444
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
def _supports_full_aniso(runtime, solver, forward_state, resolved_source_terms) -> bool:
|
|
448
|
+
"""Analytic native reverse for a full (off-diagonal) anisotropic CPML medium.
|
|
449
|
+
|
|
450
|
+
Full anisotropy adds the off-diagonal coupling ``E_i += coeff_ij * <curlH_j>``
|
|
451
|
+
on top of the diagonal CPML electric update. The reverse reuses the linear CPML
|
|
452
|
+
reverse for the diagonal step and folds the transpose of the off-diagonal
|
|
453
|
+
collocated curl(H) into the mid-step H adjoint, so the extra coupling flows the
|
|
454
|
+
cotangent through the field (H) path; the off-diagonal coefficients themselves
|
|
455
|
+
carry no material gradient channel (trainable geometry on a full-anisotropic
|
|
456
|
+
structure is guarded). Gated to the pure real, CPML class with the anisotropic
|
|
457
|
+
structure clear of the absorber (enforced by the bridge ``_full_aniso_cpml_overlap``
|
|
458
|
+
guard, so the un-stretched collocation matches the forward). Full anisotropy on
|
|
459
|
+
an open boundary, or unsupported combinations with conduction, dispersion,
|
|
460
|
+
nonlinearity, complex fields, or TFSF fail during preparation."""
|
|
461
|
+
if not getattr(solver, "full_aniso_enabled", False):
|
|
462
|
+
return False
|
|
463
|
+
if getattr(solver, "nonlinear_enabled", False):
|
|
464
|
+
return False
|
|
465
|
+
if getattr(solver, "conductive_enabled", False):
|
|
466
|
+
return False
|
|
467
|
+
if not getattr(solver, "uses_cpml", False):
|
|
468
|
+
return False
|
|
469
|
+
if getattr(solver, "dispersive_enabled", False):
|
|
470
|
+
return False
|
|
471
|
+
if getattr(solver, "magnetic_dispersive_enabled", False):
|
|
472
|
+
return False
|
|
473
|
+
if has_complex_fields(solver):
|
|
474
|
+
return False
|
|
475
|
+
if getattr(solver, "tfsf_enabled", False):
|
|
476
|
+
return False
|
|
477
|
+
if not _matches_checkpoint_layout(solver, forward_state):
|
|
478
|
+
return False
|
|
479
|
+
if _has_open_face_conflicts(_face_codes(solver)):
|
|
480
|
+
return False
|
|
481
|
+
return _supports_explicit_source_step(runtime, solver, resolved_source_terms)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _select_reverse_backend(
|
|
485
|
+
solver,
|
|
486
|
+
forward_state,
|
|
487
|
+
*,
|
|
488
|
+
eps_ex,
|
|
489
|
+
eps_ey,
|
|
490
|
+
eps_ez,
|
|
491
|
+
resolved_source_terms,
|
|
492
|
+
) -> _ReverseBackend:
|
|
493
|
+
runtime = _runtime()
|
|
494
|
+
|
|
495
|
+
supports_tfsf = _supports_tfsf(runtime, solver, forward_state, resolved_source_terms)
|
|
496
|
+
supports_grating_tfsf = _supports_grating_tfsf(runtime, solver, forward_state, resolved_source_terms)
|
|
497
|
+
supports_mixed_bloch_cpml = _supports_mixed_bloch_cpml(
|
|
498
|
+
runtime, solver, forward_state, resolved_source_terms
|
|
499
|
+
)
|
|
500
|
+
supports_cpml = _supports_cpml(runtime, solver, forward_state, resolved_source_terms)
|
|
501
|
+
supports_standard = _supports_standard(runtime, solver, forward_state, resolved_source_terms)
|
|
502
|
+
supports_dispersive = _supports_dispersive(runtime, solver, forward_state, resolved_source_terms)
|
|
503
|
+
supports_bloch = _supports_bloch(runtime, solver, forward_state, resolved_source_terms)
|
|
504
|
+
supports_bloch_dispersive = _supports_bloch_dispersive(runtime, solver, forward_state, resolved_source_terms)
|
|
505
|
+
supports_conductive = _supports_conductive(runtime, solver, forward_state, resolved_source_terms)
|
|
506
|
+
supports_kerr = _supports_kerr(runtime, solver, forward_state, resolved_source_terms)
|
|
507
|
+
supports_general_nonlinear = _supports_general_nonlinear(
|
|
508
|
+
runtime, solver, forward_state, resolved_source_terms
|
|
509
|
+
)
|
|
510
|
+
supports_full_aniso = _supports_full_aniso(runtime, solver, forward_state, resolved_source_terms)
|
|
511
|
+
|
|
512
|
+
decision_table = (
|
|
513
|
+
(_ReverseBackend.GENERAL_NONLINEAR, supports_general_nonlinear),
|
|
514
|
+
(_ReverseBackend.GRATING_TFSF, supports_grating_tfsf),
|
|
515
|
+
(_ReverseBackend.MIXED_BLOCH_CPML, supports_mixed_bloch_cpml),
|
|
516
|
+
(_ReverseBackend.TFSF, supports_tfsf),
|
|
517
|
+
(_ReverseBackend.BLOCH_DISPERSIVE, supports_bloch_dispersive),
|
|
518
|
+
(_ReverseBackend.BLOCH, supports_bloch),
|
|
519
|
+
(_ReverseBackend.DISPERSIVE, supports_dispersive),
|
|
520
|
+
(_ReverseBackend.STANDARD, supports_standard),
|
|
521
|
+
(_ReverseBackend.CPML, supports_cpml),
|
|
522
|
+
(_ReverseBackend.CONDUCTIVE, supports_conductive),
|
|
523
|
+
(_ReverseBackend.KERR, supports_kerr),
|
|
524
|
+
(_ReverseBackend.FULL_ANISO, supports_full_aniso),
|
|
525
|
+
)
|
|
526
|
+
for backend, enabled in decision_table:
|
|
527
|
+
if enabled:
|
|
528
|
+
return backend
|
|
529
|
+
raise RuntimeError(
|
|
530
|
+
"This differentiable FDTD configuration has no native CUDA adjoint variant. "
|
|
531
|
+
"Adjust the scene to a supported native capability combination."
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _accumulate_source_term_gradients(
|
|
536
|
+
runtime,
|
|
537
|
+
step_result,
|
|
538
|
+
*,
|
|
539
|
+
solver,
|
|
540
|
+
adjoint_state,
|
|
541
|
+
time_value,
|
|
542
|
+
eps_ex,
|
|
543
|
+
eps_ey,
|
|
544
|
+
eps_ez,
|
|
545
|
+
resolved_source_terms,
|
|
546
|
+
):
|
|
547
|
+
return runtime._accumulate_source_term_gradients(
|
|
548
|
+
step_result,
|
|
549
|
+
solver=solver,
|
|
550
|
+
adjoint_state=adjoint_state,
|
|
551
|
+
time_value=time_value,
|
|
552
|
+
eps_ex=eps_ex,
|
|
553
|
+
eps_ey=eps_ey,
|
|
554
|
+
eps_ez=eps_ez,
|
|
555
|
+
resolved_source_terms=resolved_source_terms,
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def _run_native_reverse_step(
|
|
560
|
+
backend,
|
|
561
|
+
solver,
|
|
562
|
+
forward_state,
|
|
563
|
+
adjoint_state,
|
|
564
|
+
*,
|
|
565
|
+
time_value,
|
|
566
|
+
eps_ex,
|
|
567
|
+
eps_ey,
|
|
568
|
+
eps_ez,
|
|
569
|
+
resolved_source_terms,
|
|
570
|
+
profiler,
|
|
571
|
+
):
|
|
572
|
+
"""Invoke the registered native CUDA reverse runner for ``backend``.
|
|
573
|
+
|
|
574
|
+
The caller guarantees ``backend`` has a registered runner. The runner owns
|
|
575
|
+
the full reverse-step contract (including any source-term gradient
|
|
576
|
+
accumulation) and returns a ``_ReverseStepResult`` labelled with the matching
|
|
577
|
+
``_NATIVE_REVERSE_LABELS`` value.
|
|
578
|
+
"""
|
|
579
|
+
if getattr(solver, "_fdtd_cuda_extension", None) is None:
|
|
580
|
+
from ..cuda.backend import get_compiled_extension
|
|
581
|
+
|
|
582
|
+
solver._fdtd_cuda_extension = get_compiled_extension()
|
|
583
|
+
runner = _NATIVE_REVERSE_RUNNERS[backend]
|
|
584
|
+
return runner(
|
|
585
|
+
solver,
|
|
586
|
+
forward_state,
|
|
587
|
+
adjoint_state,
|
|
588
|
+
time_value=time_value,
|
|
589
|
+
eps_ex=eps_ex,
|
|
590
|
+
eps_ey=eps_ey,
|
|
591
|
+
eps_ez=eps_ez,
|
|
592
|
+
resolved_source_terms=resolved_source_terms,
|
|
593
|
+
profiler=profiler,
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def validate_native_adjoint_preparation(solver) -> _ReverseBackend:
|
|
598
|
+
"""Require a complete native CUDA reverse variant before time marching."""
|
|
599
|
+
from ..checkpoint import capture_checkpoint_state
|
|
600
|
+
from .capabilities import NATIVE_ADJOINT_CAPABILITIES
|
|
601
|
+
|
|
602
|
+
state = capture_checkpoint_state(solver, step=0).tensors
|
|
603
|
+
if not _scene_is_cuda_state(state):
|
|
604
|
+
raise RuntimeError("Differentiable FDTD requires a CUDA scene.")
|
|
605
|
+
runtime = _runtime()
|
|
606
|
+
resolved = runtime._resolved_source_term_lists(
|
|
607
|
+
solver, solver.eps_Ex, solver.eps_Ey, solver.eps_Ez
|
|
608
|
+
)
|
|
609
|
+
backend = _select_reverse_backend(
|
|
610
|
+
solver, state, eps_ex=solver.eps_Ex, eps_ey=solver.eps_Ey,
|
|
611
|
+
eps_ez=solver.eps_Ez, resolved_source_terms=resolved,
|
|
612
|
+
)
|
|
613
|
+
if backend.name not in NATIVE_ADJOINT_CAPABILITIES:
|
|
614
|
+
raise RuntimeError(
|
|
615
|
+
f"Native CUDA adjoint variant {backend.name} is missing from the capability contract."
|
|
616
|
+
)
|
|
617
|
+
if not _native_backend_available(backend, solver, state):
|
|
618
|
+
raise RuntimeError(
|
|
619
|
+
"Differentiable FDTD requires the compiled native CUDA adjoint extension; "
|
|
620
|
+
f"variant {backend.name} is unavailable."
|
|
621
|
+
)
|
|
622
|
+
return backend
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def _scene_is_cuda_state(state) -> bool:
|
|
626
|
+
reference = state.get("Ex")
|
|
627
|
+
return bool(reference is not None and reference.is_cuda)
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def reverse_step(
|
|
631
|
+
solver,
|
|
632
|
+
forward_state,
|
|
633
|
+
adjoint_state,
|
|
634
|
+
*,
|
|
635
|
+
time_value,
|
|
636
|
+
eps_ex,
|
|
637
|
+
eps_ey,
|
|
638
|
+
eps_ez,
|
|
639
|
+
chi3_ex=None,
|
|
640
|
+
chi3_ey=None,
|
|
641
|
+
chi3_ez=None,
|
|
642
|
+
chi2_ex=None,
|
|
643
|
+
chi2_ey=None,
|
|
644
|
+
chi2_ez=None,
|
|
645
|
+
tpa_ex=None,
|
|
646
|
+
tpa_ey=None,
|
|
647
|
+
tpa_ez=None,
|
|
648
|
+
forward_magnetic_fields=None,
|
|
649
|
+
resolved_source_terms=None,
|
|
650
|
+
profiler=None,
|
|
651
|
+
):
|
|
652
|
+
runtime = _runtime()
|
|
653
|
+
|
|
654
|
+
state_names = tuple(forward_state.keys())
|
|
655
|
+
if tuple(adjoint_state.keys()) != state_names:
|
|
656
|
+
raise RuntimeError(
|
|
657
|
+
"Reverse step expects forward and adjoint states to share the same frozen checkpoint layout."
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
# The FDTD backward bridge resolves the source-term lists once per pass and
|
|
661
|
+
# threads them in; direct callers (parity tests) omit them and resolve here.
|
|
662
|
+
if resolved_source_terms is None:
|
|
663
|
+
resolved_source_terms = runtime._resolved_source_term_lists(solver, eps_ex, eps_ey, eps_ez)
|
|
664
|
+
analytic_backend = _select_reverse_backend(
|
|
665
|
+
solver,
|
|
666
|
+
forward_state,
|
|
667
|
+
eps_ex=eps_ex,
|
|
668
|
+
eps_ey=eps_ey,
|
|
669
|
+
eps_ez=eps_ez,
|
|
670
|
+
resolved_source_terms=resolved_source_terms,
|
|
671
|
+
)
|
|
672
|
+
if not _native_backend_available(analytic_backend, solver, forward_state):
|
|
673
|
+
raise RuntimeError(
|
|
674
|
+
"The prepared differentiable FDTD scene lost its native CUDA adjoint "
|
|
675
|
+
f"variant {analytic_backend.name}; backward cannot continue."
|
|
676
|
+
)
|
|
677
|
+
return _with_profile_sections(
|
|
678
|
+
profiler,
|
|
679
|
+
lambda: _run_native_reverse_step(
|
|
680
|
+
analytic_backend, solver, forward_state, adjoint_state,
|
|
681
|
+
time_value=time_value, eps_ex=eps_ex, eps_ey=eps_ey, eps_ez=eps_ez,
|
|
682
|
+
resolved_source_terms=resolved_source_terms, profiler=profiler,
|
|
683
|
+
),
|
|
684
|
+
)
|