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,524 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from ..boundary import has_complex_fields
|
|
9
|
+
from ..observers import get_observer_results as get_observer_results_impl
|
|
10
|
+
from ..postprocess import get_frequency_solution as get_frequency_solution_impl
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _runtime():
|
|
14
|
+
from . import core as _adjoint
|
|
15
|
+
|
|
16
|
+
return _adjoint
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class _FieldAccumulatorPair:
|
|
21
|
+
entry_index: int
|
|
22
|
+
state_field_name: str
|
|
23
|
+
real_index: int
|
|
24
|
+
imag_index: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class _PointAccumulatorPair:
|
|
29
|
+
state_field_name: str
|
|
30
|
+
global_indices: tuple[int, ...]
|
|
31
|
+
point_i: torch.Tensor
|
|
32
|
+
point_j: torch.Tensor
|
|
33
|
+
point_k: torch.Tensor
|
|
34
|
+
real_index: int
|
|
35
|
+
imag_index: int
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class _PlaneAccumulatorPair:
|
|
40
|
+
state_field_name: str
|
|
41
|
+
global_indices: tuple[int, ...]
|
|
42
|
+
axis_code: int
|
|
43
|
+
plane_index: int
|
|
44
|
+
real_index: int
|
|
45
|
+
imag_index: int
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True)
|
|
49
|
+
class _ScheduleTensorPack:
|
|
50
|
+
cos: torch.Tensor
|
|
51
|
+
sin: torch.Tensor
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True)
|
|
55
|
+
class _DenseSeedBatch:
|
|
56
|
+
state_field_name: str
|
|
57
|
+
entry_indices: torch.Tensor
|
|
58
|
+
grad_real: torch.Tensor
|
|
59
|
+
grad_imag: torch.Tensor
|
|
60
|
+
cos_pack: torch.Tensor
|
|
61
|
+
sin_pack: torch.Tensor
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(frozen=True)
|
|
65
|
+
class _PointSeedBatch:
|
|
66
|
+
state_field_name: str
|
|
67
|
+
entry_indices: torch.Tensor
|
|
68
|
+
point_i: torch.Tensor
|
|
69
|
+
point_j: torch.Tensor
|
|
70
|
+
point_k: torch.Tensor
|
|
71
|
+
grad_real: torch.Tensor
|
|
72
|
+
grad_imag: torch.Tensor
|
|
73
|
+
cos_pack: torch.Tensor
|
|
74
|
+
sin_pack: torch.Tensor
|
|
75
|
+
point_i32: torch.Tensor
|
|
76
|
+
point_j32: torch.Tensor
|
|
77
|
+
point_k32: torch.Tensor
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True)
|
|
81
|
+
class _PlaneSeedBatch:
|
|
82
|
+
state_field_name: str
|
|
83
|
+
axis_code: int
|
|
84
|
+
plane_index: int
|
|
85
|
+
entry_indices: torch.Tensor
|
|
86
|
+
grad_real: torch.Tensor
|
|
87
|
+
grad_imag: torch.Tensor
|
|
88
|
+
cos_pack: torch.Tensor
|
|
89
|
+
sin_pack: torch.Tensor
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass(frozen=True)
|
|
93
|
+
class _SeedRuntime:
|
|
94
|
+
dft_schedule: _ScheduleTensorPack
|
|
95
|
+
observer_schedule: _ScheduleTensorPack
|
|
96
|
+
dense_batches: tuple[_DenseSeedBatch, ...]
|
|
97
|
+
point_batches: tuple[_PointSeedBatch, ...]
|
|
98
|
+
plane_batches: tuple[_PlaneSeedBatch, ...]
|
|
99
|
+
backend: str = "device_batched"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _schedule_to_tensor_pack(schedules, *, device, dtype) -> _ScheduleTensorPack:
|
|
103
|
+
if not schedules:
|
|
104
|
+
empty = torch.zeros((0, 0), device=device, dtype=dtype)
|
|
105
|
+
return _ScheduleTensorPack(cos=empty, sin=empty)
|
|
106
|
+
cos = torch.tensor(
|
|
107
|
+
[[float(weight_cos) for weight_cos, _weight_sin in entry] for entry in schedules],
|
|
108
|
+
device=device,
|
|
109
|
+
dtype=dtype,
|
|
110
|
+
)
|
|
111
|
+
sin = torch.tensor(
|
|
112
|
+
[[float(weight_sin) for _weight_cos, weight_sin in entry] for entry in schedules],
|
|
113
|
+
device=device,
|
|
114
|
+
dtype=dtype,
|
|
115
|
+
)
|
|
116
|
+
return _ScheduleTensorPack(cos=cos, sin=sin)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _reshape_entry_major_grad(grad: torch.Tensor, entry_count: int) -> torch.Tensor:
|
|
120
|
+
if entry_count <= 0:
|
|
121
|
+
raise ValueError("Seed batch must contain at least one entry.")
|
|
122
|
+
detached = grad.detach()
|
|
123
|
+
if entry_count == 1:
|
|
124
|
+
if detached.ndim == 0 or detached.shape[0] != 1:
|
|
125
|
+
detached = detached.unsqueeze(0)
|
|
126
|
+
return detached.contiguous()
|
|
127
|
+
if detached.ndim == 0 or detached.shape[0] != entry_count:
|
|
128
|
+
raise RuntimeError(
|
|
129
|
+
"Seed gradient layout does not match the observer/global frequency index structure."
|
|
130
|
+
)
|
|
131
|
+
return detached.contiguous()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _stack_dense_seed_records(records) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
|
135
|
+
entry_indices = torch.tensor(
|
|
136
|
+
[int(entry_index) for entry_index, _grad_real, _grad_imag in records],
|
|
137
|
+
device=records[0][1].device,
|
|
138
|
+
dtype=torch.long,
|
|
139
|
+
)
|
|
140
|
+
grad_real = torch.stack([grad_real.detach() for _entry_index, grad_real, _grad_imag in records], dim=0).contiguous()
|
|
141
|
+
grad_imag = torch.stack([grad_imag.detach() for _entry_index, _grad_real, grad_imag in records], dim=0).contiguous()
|
|
142
|
+
return entry_indices, grad_real, grad_imag
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _seed_batch_contribution(
|
|
146
|
+
grad_real: torch.Tensor,
|
|
147
|
+
grad_imag: torch.Tensor,
|
|
148
|
+
weight_cos: torch.Tensor,
|
|
149
|
+
weight_sin: torch.Tensor,
|
|
150
|
+
) -> torch.Tensor:
|
|
151
|
+
view_shape = (int(weight_cos.shape[0]),) + (1,) * max(grad_real.ndim - 1, 0)
|
|
152
|
+
return torch.sum(
|
|
153
|
+
grad_real * weight_cos.view(view_shape) + grad_imag * weight_sin.view(view_shape),
|
|
154
|
+
dim=0,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _clone_seed_solver(solver):
|
|
159
|
+
seed_solver = SimpleNamespace()
|
|
160
|
+
seed_solver.scene = solver.scene
|
|
161
|
+
seed_solver.verbose = False
|
|
162
|
+
seed_solver.complex_fields_enabled = has_complex_fields(solver)
|
|
163
|
+
seed_solver._normalize_source = getattr(solver, "_normalize_source", False)
|
|
164
|
+
seed_solver._source_time = getattr(solver, "_source_time", None)
|
|
165
|
+
seed_solver.dft_window_type = getattr(solver, "dft_window_type", "none")
|
|
166
|
+
seed_solver.observer_window_type = getattr(solver, "observer_window_type", "none")
|
|
167
|
+
seed_solver.observers_enabled = bool(getattr(solver, "observers_enabled", False))
|
|
168
|
+
seed_solver.observers = list(getattr(solver, "observers", ()))
|
|
169
|
+
seed_solver._point_observer_groups = {}
|
|
170
|
+
seed_solver._plane_observer_groups = {}
|
|
171
|
+
|
|
172
|
+
leaves = []
|
|
173
|
+
field_pairs = []
|
|
174
|
+
point_pairs = []
|
|
175
|
+
plane_pairs = []
|
|
176
|
+
|
|
177
|
+
seed_solver._dft_entries = []
|
|
178
|
+
for entry_index, entry in enumerate(getattr(solver, "_dft_entries", ())):
|
|
179
|
+
cloned_entry = dict(entry)
|
|
180
|
+
cloned_fields = {}
|
|
181
|
+
for field_name in ("Ex", "Ey", "Ez"):
|
|
182
|
+
payload = entry["fields"][field_name]
|
|
183
|
+
real = payload["real"].detach().clone().requires_grad_(True)
|
|
184
|
+
imag = payload["imag"].detach().clone().requires_grad_(True)
|
|
185
|
+
real_index = len(leaves)
|
|
186
|
+
imag_index = len(leaves) + 1
|
|
187
|
+
leaves.extend([real, imag])
|
|
188
|
+
field_pairs.append(
|
|
189
|
+
_FieldAccumulatorPair(
|
|
190
|
+
entry_index=entry_index,
|
|
191
|
+
state_field_name=field_name,
|
|
192
|
+
real_index=real_index,
|
|
193
|
+
imag_index=imag_index,
|
|
194
|
+
)
|
|
195
|
+
)
|
|
196
|
+
aux_real = None
|
|
197
|
+
aux_imag = None
|
|
198
|
+
if payload.get("aux_real") is not None and payload.get("aux_imag") is not None:
|
|
199
|
+
aux_real = payload["aux_real"].detach().clone().requires_grad_(True)
|
|
200
|
+
aux_imag = payload["aux_imag"].detach().clone().requires_grad_(True)
|
|
201
|
+
aux_real_index = len(leaves)
|
|
202
|
+
aux_imag_index = len(leaves) + 1
|
|
203
|
+
leaves.extend([aux_real, aux_imag])
|
|
204
|
+
field_pairs.append(
|
|
205
|
+
_FieldAccumulatorPair(
|
|
206
|
+
entry_index=entry_index,
|
|
207
|
+
state_field_name=f"{field_name}_imag",
|
|
208
|
+
real_index=aux_real_index,
|
|
209
|
+
imag_index=aux_imag_index,
|
|
210
|
+
)
|
|
211
|
+
)
|
|
212
|
+
cloned_fields[field_name] = {
|
|
213
|
+
"real": real,
|
|
214
|
+
"imag": imag,
|
|
215
|
+
"aux_real": aux_real,
|
|
216
|
+
"aux_imag": aux_imag,
|
|
217
|
+
}
|
|
218
|
+
cloned_entry["fields"] = cloned_fields
|
|
219
|
+
seed_solver._dft_entries.append(cloned_entry)
|
|
220
|
+
|
|
221
|
+
seed_solver._observer_spectral_entries = [dict(entry) for entry in getattr(solver, "_observer_spectral_entries", ())]
|
|
222
|
+
|
|
223
|
+
for group_key, group in getattr(solver, "_point_observer_groups", {}).items():
|
|
224
|
+
cloned_group = dict(group)
|
|
225
|
+
real = group["real"].detach().clone().requires_grad_(True)
|
|
226
|
+
imag = group["imag"].detach().clone().requires_grad_(True)
|
|
227
|
+
real_index = len(leaves)
|
|
228
|
+
imag_index = len(leaves) + 1
|
|
229
|
+
leaves.extend([real, imag])
|
|
230
|
+
point_pairs.append(
|
|
231
|
+
_PointAccumulatorPair(
|
|
232
|
+
state_field_name=group["field_name"],
|
|
233
|
+
global_indices=tuple(group["global_freq_indices"]),
|
|
234
|
+
point_i=group["point_i"].to(dtype=torch.long),
|
|
235
|
+
point_j=group["point_j"].to(dtype=torch.long),
|
|
236
|
+
point_k=group["point_k"].to(dtype=torch.long),
|
|
237
|
+
real_index=real_index,
|
|
238
|
+
imag_index=imag_index,
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
cloned_group["real"] = real
|
|
242
|
+
cloned_group["imag"] = imag
|
|
243
|
+
if "aux_real" in group and "aux_imag" in group:
|
|
244
|
+
aux_real = group["aux_real"].detach().clone().requires_grad_(True)
|
|
245
|
+
aux_imag = group["aux_imag"].detach().clone().requires_grad_(True)
|
|
246
|
+
aux_real_index = len(leaves)
|
|
247
|
+
aux_imag_index = len(leaves) + 1
|
|
248
|
+
leaves.extend([aux_real, aux_imag])
|
|
249
|
+
point_pairs.append(
|
|
250
|
+
_PointAccumulatorPair(
|
|
251
|
+
state_field_name=f"{group['field_name']}_imag",
|
|
252
|
+
global_indices=tuple(group["global_freq_indices"]),
|
|
253
|
+
point_i=group["point_i"].to(dtype=torch.long),
|
|
254
|
+
point_j=group["point_j"].to(dtype=torch.long),
|
|
255
|
+
point_k=group["point_k"].to(dtype=torch.long),
|
|
256
|
+
real_index=aux_real_index,
|
|
257
|
+
imag_index=aux_imag_index,
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
cloned_group["aux_real"] = aux_real
|
|
261
|
+
cloned_group["aux_imag"] = aux_imag
|
|
262
|
+
seed_solver._point_observer_groups[group_key] = cloned_group
|
|
263
|
+
|
|
264
|
+
seed_solver._plane_observer_groups = {}
|
|
265
|
+
for group_key, group in getattr(solver, "_plane_observer_groups", {}).items():
|
|
266
|
+
cloned_group = dict(group)
|
|
267
|
+
real = group["real"].detach().clone().requires_grad_(True)
|
|
268
|
+
imag = group["imag"].detach().clone().requires_grad_(True)
|
|
269
|
+
real_index = len(leaves)
|
|
270
|
+
imag_index = len(leaves) + 1
|
|
271
|
+
leaves.extend([real, imag])
|
|
272
|
+
plane_pairs.append(
|
|
273
|
+
_PlaneAccumulatorPair(
|
|
274
|
+
state_field_name=group["field_name"],
|
|
275
|
+
global_indices=tuple(group["global_freq_indices"]),
|
|
276
|
+
axis_code=int(group["axis_code"]),
|
|
277
|
+
plane_index=int(group["plane_index"]),
|
|
278
|
+
real_index=real_index,
|
|
279
|
+
imag_index=imag_index,
|
|
280
|
+
)
|
|
281
|
+
)
|
|
282
|
+
cloned_group["real"] = real
|
|
283
|
+
cloned_group["imag"] = imag
|
|
284
|
+
if "aux_real" in group and "aux_imag" in group:
|
|
285
|
+
aux_real = group["aux_real"].detach().clone().requires_grad_(True)
|
|
286
|
+
aux_imag = group["aux_imag"].detach().clone().requires_grad_(True)
|
|
287
|
+
aux_real_index = len(leaves)
|
|
288
|
+
aux_imag_index = len(leaves) + 1
|
|
289
|
+
leaves.extend([aux_real, aux_imag])
|
|
290
|
+
plane_pairs.append(
|
|
291
|
+
_PlaneAccumulatorPair(
|
|
292
|
+
state_field_name=f"{group['field_name']}_imag",
|
|
293
|
+
global_indices=tuple(group["global_freq_indices"]),
|
|
294
|
+
axis_code=int(group["axis_code"]),
|
|
295
|
+
plane_index=int(group["plane_index"]),
|
|
296
|
+
real_index=aux_real_index,
|
|
297
|
+
imag_index=aux_imag_index,
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
cloned_group["aux_real"] = aux_real
|
|
301
|
+
cloned_group["aux_imag"] = aux_imag
|
|
302
|
+
seed_solver._plane_observer_groups[group_key] = cloned_group
|
|
303
|
+
|
|
304
|
+
return seed_solver, tuple(leaves), field_pairs, point_pairs, plane_pairs
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def _dense_seed_output_pairs(seed_solver):
|
|
308
|
+
runtime = _runtime()
|
|
309
|
+
raw_output = {}
|
|
310
|
+
if getattr(seed_solver, "_dft_entries", ()):
|
|
311
|
+
raw_output.update(
|
|
312
|
+
get_frequency_solution_impl(
|
|
313
|
+
seed_solver,
|
|
314
|
+
all_frequencies=True,
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
if getattr(seed_solver, "observers_enabled", False):
|
|
318
|
+
raw_output["observers"] = get_observer_results_impl(seed_solver)
|
|
319
|
+
return runtime._prepare_forward_pack(raw_output)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _build_output_seeds(
|
|
323
|
+
solver,
|
|
324
|
+
pack,
|
|
325
|
+
grad_outputs,
|
|
326
|
+
*,
|
|
327
|
+
dft_schedule: _ScheduleTensorPack,
|
|
328
|
+
observer_schedule: _ScheduleTensorPack,
|
|
329
|
+
) -> _SeedRuntime:
|
|
330
|
+
runtime = _runtime()
|
|
331
|
+
with torch.enable_grad():
|
|
332
|
+
seed_solver, leaves, field_pairs, point_pairs, plane_pairs = _clone_seed_solver(solver)
|
|
333
|
+
seed_pack = _dense_seed_output_pairs(seed_solver)
|
|
334
|
+
if len(seed_pack.output_tensors) != len(pack.output_tensors):
|
|
335
|
+
raise RuntimeError("Adjoint output pack layout changed between forward and backward.")
|
|
336
|
+
output_grads = tuple(
|
|
337
|
+
torch.zeros_like(output) if grad_output is None else grad_output.to(device=output.device, dtype=output.dtype)
|
|
338
|
+
for output, grad_output in zip(seed_pack.output_tensors, grad_outputs)
|
|
339
|
+
)
|
|
340
|
+
leaf_grads = torch.autograd.grad(
|
|
341
|
+
seed_pack.output_tensors,
|
|
342
|
+
leaves,
|
|
343
|
+
grad_outputs=output_grads,
|
|
344
|
+
allow_unused=True,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
dense_seed_records: dict[str, list[tuple[int, torch.Tensor, torch.Tensor]]] = {}
|
|
348
|
+
for pair in field_pairs:
|
|
349
|
+
real_leaf = leaves[pair.real_index]
|
|
350
|
+
imag_leaf = leaves[pair.imag_index]
|
|
351
|
+
dense_seed_records.setdefault(pair.state_field_name, []).append(
|
|
352
|
+
(
|
|
353
|
+
int(pair.entry_index),
|
|
354
|
+
runtime._safe_grad(leaf_grads[pair.real_index], real_leaf),
|
|
355
|
+
runtime._safe_grad(leaf_grads[pair.imag_index], imag_leaf),
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
dense_batches = []
|
|
360
|
+
for state_field_name, records in dense_seed_records.items():
|
|
361
|
+
entry_indices, grad_real, grad_imag = _stack_dense_seed_records(records)
|
|
362
|
+
cos_pack, sin_pack = _schedule_pack_for(dft_schedule, entry_indices)
|
|
363
|
+
dense_batches.append(
|
|
364
|
+
_DenseSeedBatch(
|
|
365
|
+
state_field_name=state_field_name,
|
|
366
|
+
entry_indices=entry_indices,
|
|
367
|
+
grad_real=grad_real,
|
|
368
|
+
grad_imag=grad_imag,
|
|
369
|
+
cos_pack=cos_pack,
|
|
370
|
+
sin_pack=sin_pack,
|
|
371
|
+
)
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
point_batches = []
|
|
375
|
+
for pair in point_pairs:
|
|
376
|
+
entry_count = len(pair.global_indices)
|
|
377
|
+
real_grad = _reshape_entry_major_grad(
|
|
378
|
+
runtime._safe_grad(leaf_grads[pair.real_index], leaves[pair.real_index]),
|
|
379
|
+
entry_count,
|
|
380
|
+
)
|
|
381
|
+
imag_grad = _reshape_entry_major_grad(
|
|
382
|
+
runtime._safe_grad(leaf_grads[pair.imag_index], leaves[pair.imag_index]),
|
|
383
|
+
entry_count,
|
|
384
|
+
)
|
|
385
|
+
entry_indices = torch.tensor(pair.global_indices, device=real_grad.device, dtype=torch.long)
|
|
386
|
+
cos_pack, sin_pack = _schedule_pack_for(observer_schedule, entry_indices)
|
|
387
|
+
point_i = pair.point_i.to(device=real_grad.device, dtype=torch.long)
|
|
388
|
+
point_j = pair.point_j.to(device=real_grad.device, dtype=torch.long)
|
|
389
|
+
point_k = pair.point_k.to(device=real_grad.device, dtype=torch.long)
|
|
390
|
+
point_batches.append(
|
|
391
|
+
_PointSeedBatch(
|
|
392
|
+
state_field_name=pair.state_field_name,
|
|
393
|
+
entry_indices=entry_indices,
|
|
394
|
+
point_i=point_i,
|
|
395
|
+
point_j=point_j,
|
|
396
|
+
point_k=point_k,
|
|
397
|
+
grad_real=real_grad,
|
|
398
|
+
grad_imag=imag_grad,
|
|
399
|
+
cos_pack=cos_pack,
|
|
400
|
+
sin_pack=sin_pack,
|
|
401
|
+
point_i32=point_i.to(dtype=torch.int32),
|
|
402
|
+
point_j32=point_j.to(dtype=torch.int32),
|
|
403
|
+
point_k32=point_k.to(dtype=torch.int32),
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
plane_batches = []
|
|
408
|
+
for pair in plane_pairs:
|
|
409
|
+
entry_count = len(pair.global_indices)
|
|
410
|
+
real_grad = _reshape_entry_major_grad(
|
|
411
|
+
runtime._safe_grad(leaf_grads[pair.real_index], leaves[pair.real_index]),
|
|
412
|
+
entry_count,
|
|
413
|
+
)
|
|
414
|
+
imag_grad = _reshape_entry_major_grad(
|
|
415
|
+
runtime._safe_grad(leaf_grads[pair.imag_index], leaves[pair.imag_index]),
|
|
416
|
+
entry_count,
|
|
417
|
+
)
|
|
418
|
+
entry_indices = torch.tensor(pair.global_indices, device=real_grad.device, dtype=torch.long)
|
|
419
|
+
cos_pack, sin_pack = _schedule_pack_for(observer_schedule, entry_indices)
|
|
420
|
+
plane_batches.append(
|
|
421
|
+
_PlaneSeedBatch(
|
|
422
|
+
state_field_name=pair.state_field_name,
|
|
423
|
+
axis_code=pair.axis_code,
|
|
424
|
+
plane_index=pair.plane_index,
|
|
425
|
+
entry_indices=entry_indices,
|
|
426
|
+
grad_real=real_grad,
|
|
427
|
+
grad_imag=imag_grad,
|
|
428
|
+
cos_pack=cos_pack,
|
|
429
|
+
sin_pack=sin_pack,
|
|
430
|
+
)
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
return _SeedRuntime(
|
|
434
|
+
dft_schedule=dft_schedule,
|
|
435
|
+
observer_schedule=observer_schedule,
|
|
436
|
+
dense_batches=tuple(dense_batches),
|
|
437
|
+
point_batches=tuple(point_batches),
|
|
438
|
+
plane_batches=tuple(plane_batches),
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _schedule_pack_for(
|
|
443
|
+
schedule: _ScheduleTensorPack, entry_indices: torch.Tensor
|
|
444
|
+
) -> tuple[torch.Tensor, torch.Tensor]:
|
|
445
|
+
"""Gather the per-entry schedule rows ``(E, T)`` for a seed batch once.
|
|
446
|
+
|
|
447
|
+
The native seed-injection kernels read column ``step`` of these packs per
|
|
448
|
+
reverse step, so the (small) per-entry ``index_select`` runs once at seed
|
|
449
|
+
build instead of every step. Degenerate empty schedules collapse to an
|
|
450
|
+
``(E, 0)`` pack so the native apply skips the batch (zero contribution),
|
|
451
|
+
matching the Torch reference's empty-weight guard.
|
|
452
|
+
"""
|
|
453
|
+
if entry_indices.numel() == 0 or schedule.cos.numel() == 0:
|
|
454
|
+
empty = torch.zeros((int(entry_indices.numel()), 0), device=entry_indices.device, dtype=schedule.cos.dtype)
|
|
455
|
+
return empty, empty
|
|
456
|
+
cos = schedule.cos.index_select(0, entry_indices).contiguous()
|
|
457
|
+
sin = schedule.sin.index_select(0, entry_indices).contiguous()
|
|
458
|
+
return cos, sin
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _apply_dense_seeds_native(_cuda_backend, adj_state, seed_runtime: _SeedRuntime, step_index):
|
|
462
|
+
for batch in seed_runtime.dense_batches:
|
|
463
|
+
if batch.cos_pack.shape[1] == 0:
|
|
464
|
+
continue
|
|
465
|
+
_cuda_backend._seed_inject_dense(
|
|
466
|
+
AdjField=adj_state[batch.state_field_name],
|
|
467
|
+
GradReal=batch.grad_real,
|
|
468
|
+
GradImag=batch.grad_imag,
|
|
469
|
+
CosPack=batch.cos_pack,
|
|
470
|
+
SinPack=batch.sin_pack,
|
|
471
|
+
step=step_index,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def _apply_point_seeds_native(_cuda_backend, adj_state, seed_runtime: _SeedRuntime, step_index):
|
|
476
|
+
for batch in seed_runtime.point_batches:
|
|
477
|
+
if batch.cos_pack.shape[1] == 0:
|
|
478
|
+
continue
|
|
479
|
+
_cuda_backend._seed_inject_point(
|
|
480
|
+
AdjField=adj_state[batch.state_field_name],
|
|
481
|
+
GradReal=batch.grad_real,
|
|
482
|
+
GradImag=batch.grad_imag,
|
|
483
|
+
PointI=batch.point_i32,
|
|
484
|
+
PointJ=batch.point_j32,
|
|
485
|
+
PointK=batch.point_k32,
|
|
486
|
+
CosPack=batch.cos_pack,
|
|
487
|
+
SinPack=batch.sin_pack,
|
|
488
|
+
step=step_index,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def _apply_plane_seeds_native(_cuda_backend, adj_state, seed_runtime: _SeedRuntime, step_index):
|
|
493
|
+
for batch in seed_runtime.plane_batches:
|
|
494
|
+
if batch.cos_pack.shape[1] == 0:
|
|
495
|
+
continue
|
|
496
|
+
_cuda_backend._seed_inject_plane(
|
|
497
|
+
AdjField=adj_state[batch.state_field_name],
|
|
498
|
+
GradReal=batch.grad_real,
|
|
499
|
+
GradImag=batch.grad_imag,
|
|
500
|
+
CosPack=batch.cos_pack,
|
|
501
|
+
SinPack=batch.sin_pack,
|
|
502
|
+
axis=batch.axis_code,
|
|
503
|
+
planeIndex=batch.plane_index,
|
|
504
|
+
step=step_index,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
def _seed_runtime_native_backend(adj_state):
|
|
509
|
+
"""Require the CUDA module used by adjoint seed-injection kernels."""
|
|
510
|
+
reference = next(iter(adj_state.values()), None)
|
|
511
|
+
if reference is None or not reference.is_cuda:
|
|
512
|
+
raise RuntimeError("FDTD adjoint seed injection requires CUDA tensors.")
|
|
513
|
+
from ..cuda import backend as _cuda_backend
|
|
514
|
+
|
|
515
|
+
if not _cuda_backend.is_available():
|
|
516
|
+
raise RuntimeError("FDTD adjoint seed injection requires the native CUDA extension.")
|
|
517
|
+
return _cuda_backend
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def _apply_seed_runtime(adj_state, seed_runtime: _SeedRuntime, step_index):
|
|
521
|
+
cuda_backend = _seed_runtime_native_backend(adj_state)
|
|
522
|
+
_apply_dense_seeds_native(cuda_backend, adj_state, seed_runtime, step_index)
|
|
523
|
+
_apply_point_seeds_native(cuda_backend, adj_state, seed_runtime, step_index)
|
|
524
|
+
_apply_plane_seeds_native(cuda_backend, adj_state, seed_runtime, step_index)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from .common import (
|
|
2
|
+
BOUNDARY_BLOCH,
|
|
3
|
+
BOUNDARY_KIND_TO_CODE,
|
|
4
|
+
BOUNDARY_NONE,
|
|
5
|
+
BOUNDARY_PEC,
|
|
6
|
+
BOUNDARY_PERIODIC,
|
|
7
|
+
BOUNDARY_PMC,
|
|
8
|
+
BOUNDARY_PML,
|
|
9
|
+
combine_complex_spectral_components,
|
|
10
|
+
has_complex_fields,
|
|
11
|
+
)
|
|
12
|
+
from .cpml import (
|
|
13
|
+
DEFAULT_CPML_CONFIG,
|
|
14
|
+
expand_cpml_memory_tensor,
|
|
15
|
+
initialize_cpml_state,
|
|
16
|
+
initialize_neutral_boundary_state,
|
|
17
|
+
initialize_simple_pml_state,
|
|
18
|
+
)
|
|
19
|
+
from .runtime import initialize_boundary_state
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"BOUNDARY_BLOCH",
|
|
23
|
+
"BOUNDARY_KIND_TO_CODE",
|
|
24
|
+
"BOUNDARY_NONE",
|
|
25
|
+
"BOUNDARY_PEC",
|
|
26
|
+
"BOUNDARY_PERIODIC",
|
|
27
|
+
"BOUNDARY_PMC",
|
|
28
|
+
"BOUNDARY_PML",
|
|
29
|
+
"DEFAULT_CPML_CONFIG",
|
|
30
|
+
"combine_complex_spectral_components",
|
|
31
|
+
"expand_cpml_memory_tensor",
|
|
32
|
+
"has_complex_fields",
|
|
33
|
+
"initialize_boundary_state",
|
|
34
|
+
"initialize_cpml_state",
|
|
35
|
+
"initialize_neutral_boundary_state",
|
|
36
|
+
"initialize_simple_pml_state",
|
|
37
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
|
|
5
|
+
from ..dispersion import solve_numerical_wavenumber
|
|
6
|
+
|
|
7
|
+
_AXES = ("x", "y", "z")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _domain_lengths(solver) -> tuple[float, float, float]:
|
|
11
|
+
domain_range = solver.scene.domain_range
|
|
12
|
+
return (
|
|
13
|
+
float(domain_range[1] - domain_range[0]),
|
|
14
|
+
float(domain_range[3] - domain_range[2]),
|
|
15
|
+
float(domain_range[5] - domain_range[4]),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _bloch_axes(solver) -> tuple[str, ...]:
|
|
20
|
+
return tuple(axis for axis in _AXES if solver.scene.boundary.axis_kind(axis) == "bloch")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _tfsf_slab_sources(solver) -> list[dict]:
|
|
24
|
+
return [
|
|
25
|
+
source
|
|
26
|
+
for source in getattr(solver, "_compiled_sources", ()) or ()
|
|
27
|
+
if source.get("injection", {}).get("kind") == "tfsf"
|
|
28
|
+
and source.get("injection", {}).get("mode") == "slab"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def validate_grating_tfsf_slab_topology(solver) -> dict:
|
|
33
|
+
sources = _tfsf_slab_sources(solver)
|
|
34
|
+
if len(sources) != 1:
|
|
35
|
+
raise ValueError("Mixed Bloch/PML TFSF slab support requires exactly one TFSF slab source.")
|
|
36
|
+
source = sources[0]
|
|
37
|
+
if source.get("kind") != "plane_wave":
|
|
38
|
+
raise ValueError("Mixed Bloch/PML TFSF slab support requires a PlaneWave source.")
|
|
39
|
+
|
|
40
|
+
injection = source["injection"]
|
|
41
|
+
normal_axis = injection["axis"]
|
|
42
|
+
boundary = solver.scene.boundary
|
|
43
|
+
normal_faces = boundary.axis_face_kinds(normal_axis)
|
|
44
|
+
if boundary.axis_kind(normal_axis) == "bloch" or normal_faces != ("pml", "pml"):
|
|
45
|
+
raise ValueError("Mixed Bloch/PML TFSF slab support requires the TFSF slab normal axis to use PML.")
|
|
46
|
+
|
|
47
|
+
expected_bloch_axes = tuple(axis for axis in _AXES if axis != normal_axis)
|
|
48
|
+
if _bloch_axes(solver) != expected_bloch_axes:
|
|
49
|
+
raise ValueError("Mixed Bloch/PML TFSF slab support requires Bloch axes transverse to the TFSF slab.")
|
|
50
|
+
return source
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _require_auto_bloch_source(solver) -> dict:
|
|
54
|
+
source = validate_grating_tfsf_slab_topology(solver)
|
|
55
|
+
if source["source_time"]["kind"] != "cw":
|
|
56
|
+
raise ValueError("Automatic Bloch wavevector resolution requires a CW PlaneWave source_time.")
|
|
57
|
+
return source
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def resolve_bloch_wavevector(solver) -> tuple[float, float, float]:
|
|
61
|
+
boundary = solver.scene.boundary
|
|
62
|
+
wavevector = boundary.bloch_wavevector
|
|
63
|
+
if wavevector != "auto":
|
|
64
|
+
return tuple(float(component) for component in wavevector)
|
|
65
|
+
|
|
66
|
+
source = _require_auto_bloch_source(solver)
|
|
67
|
+
k_numeric = solve_numerical_wavenumber(
|
|
68
|
+
solver,
|
|
69
|
+
source["direction"],
|
|
70
|
+
{"x": solver.min_dx, "y": solver.min_dy, "z": solver.min_dz},
|
|
71
|
+
)
|
|
72
|
+
bloch_axes = set(_bloch_axes(solver))
|
|
73
|
+
return tuple(
|
|
74
|
+
float(k_numeric * float(component)) if axis in bloch_axes else 0.0
|
|
75
|
+
for axis, component in zip(_AXES, source["direction"])
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def resolve_bloch_phase_factors(solver) -> tuple[complex, complex, complex]:
|
|
80
|
+
wavevector = getattr(solver, "resolved_bloch_wavevector", None)
|
|
81
|
+
if wavevector is None:
|
|
82
|
+
wavevector = resolve_bloch_wavevector(solver)
|
|
83
|
+
phases = []
|
|
84
|
+
for axis, wave_number, length in zip(_AXES, wavevector, _domain_lengths(solver)):
|
|
85
|
+
if solver.scene.boundary.axis_kind(axis) == "bloch":
|
|
86
|
+
angle = float(wave_number) * float(length)
|
|
87
|
+
phases.append(complex(math.cos(angle), math.sin(angle)))
|
|
88
|
+
else:
|
|
89
|
+
phases.append(1.0 + 0.0j)
|
|
90
|
+
return tuple(phases)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
|
|
5
|
+
BOUNDARY_NONE = 0
|
|
6
|
+
BOUNDARY_PML = 1
|
|
7
|
+
BOUNDARY_PERIODIC = 2
|
|
8
|
+
BOUNDARY_BLOCH = 3
|
|
9
|
+
BOUNDARY_PEC = 4
|
|
10
|
+
BOUNDARY_PMC = 5
|
|
11
|
+
|
|
12
|
+
BOUNDARY_KIND_TO_CODE = {
|
|
13
|
+
"none": BOUNDARY_NONE,
|
|
14
|
+
"pml": BOUNDARY_PML,
|
|
15
|
+
"periodic": BOUNDARY_PERIODIC,
|
|
16
|
+
"bloch": BOUNDARY_BLOCH,
|
|
17
|
+
"pec": BOUNDARY_PEC,
|
|
18
|
+
"pmc": BOUNDARY_PMC,
|
|
19
|
+
# Mur ABC is applied in PyTorch after the E-update; the update kernel treats
|
|
20
|
+
# these faces as inert so no dedicated CUDA boundary code is required.
|
|
21
|
+
"mur": BOUNDARY_NONE,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def has_complex_fields(solver) -> bool:
|
|
26
|
+
return bool(getattr(solver, "complex_fields_enabled", False))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def combine_complex_spectral_components(real_field_real, real_field_imag, imag_field_real=None, imag_field_imag=None):
|
|
30
|
+
if imag_field_real is None or imag_field_imag is None:
|
|
31
|
+
return real_field_real + 1j * real_field_imag
|
|
32
|
+
return (real_field_real - imag_field_imag) + 1j * (real_field_imag + imag_field_real)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def initialize_complex_fields(solver):
|
|
36
|
+
solver.Ex_imag = torch.zeros_like(solver.Ex)
|
|
37
|
+
solver.Ey_imag = torch.zeros_like(solver.Ey)
|
|
38
|
+
solver.Ez_imag = torch.zeros_like(solver.Ez)
|
|
39
|
+
solver.Hx_imag = torch.zeros_like(solver.Hx)
|
|
40
|
+
solver.Hy_imag = torch.zeros_like(solver.Hy)
|
|
41
|
+
solver.Hz_imag = torch.zeros_like(solver.Hz)
|