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,778 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import torch
|
|
5
|
+
|
|
6
|
+
from ..boundary.bloch import validate_grating_tfsf_slab_topology
|
|
7
|
+
from .spatial import AuxiliaryGrid1D
|
|
8
|
+
from .tfsf_common import (
|
|
9
|
+
box_center_tensor,
|
|
10
|
+
build_batched_aux_terms,
|
|
11
|
+
build_batched_reference_terms,
|
|
12
|
+
build_term_from_profile,
|
|
13
|
+
build_terms_from_specs,
|
|
14
|
+
entry_projection,
|
|
15
|
+
incident_profile,
|
|
16
|
+
make_aux_term,
|
|
17
|
+
nearest_index,
|
|
18
|
+
project_positions,
|
|
19
|
+
projection_extrema,
|
|
20
|
+
require_locally_uniform_axis,
|
|
21
|
+
resolve_bounds_indices,
|
|
22
|
+
solve_auxiliary_step,
|
|
23
|
+
solve_numerical_wavenumber,
|
|
24
|
+
validate_background_is_vacuum,
|
|
25
|
+
validate_bounds,
|
|
26
|
+
)
|
|
27
|
+
from .tfsf_specs import (
|
|
28
|
+
AXIS_INDEX,
|
|
29
|
+
E_CURL_ATTR,
|
|
30
|
+
H_CURL_ATTR,
|
|
31
|
+
axis_aligned_direction,
|
|
32
|
+
axis_aligned_sample_indices,
|
|
33
|
+
axis_aligned_sample_view,
|
|
34
|
+
build_discrete_tfsf_specs,
|
|
35
|
+
build_slab_tfsf_specs,
|
|
36
|
+
constant_line_index_tensor,
|
|
37
|
+
discrete_plane_wave_vectors,
|
|
38
|
+
is_reference_plane_wave_x_ez,
|
|
39
|
+
line_index_tensor,
|
|
40
|
+
magnetic_physical_vector,
|
|
41
|
+
magnetic_unit_vector,
|
|
42
|
+
make_reference_term,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
ABSORBER_CELLS = 20
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _domain_bounds(solver):
|
|
50
|
+
return (
|
|
51
|
+
(solver.scene.domain_range[0], solver.scene.domain_range[1]),
|
|
52
|
+
(solver.scene.domain_range[2], solver.scene.domain_range[3]),
|
|
53
|
+
(solver.scene.domain_range[4], solver.scene.domain_range[5]),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _make_auxiliary_grid(solver, *, s_min, s_max, ds):
|
|
58
|
+
eta0 = (solver.mu0 / solver.eps0) ** 0.5
|
|
59
|
+
return AuxiliaryGrid1D(
|
|
60
|
+
s_min=s_min,
|
|
61
|
+
s_max=s_max + ABSORBER_CELLS * ds,
|
|
62
|
+
ds=ds,
|
|
63
|
+
dt=solver.dt,
|
|
64
|
+
wave_speed=solver.c,
|
|
65
|
+
impedance=eta0,
|
|
66
|
+
source_time=solver._source_time,
|
|
67
|
+
device=solver.device,
|
|
68
|
+
dtype=solver.Ex.dtype,
|
|
69
|
+
absorber_cells=ABSORBER_CELLS,
|
|
70
|
+
fdtd_module=solver.fdtd_module,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _make_directional_auxiliary_grid(solver, direction, ds):
|
|
75
|
+
s_min_domain, s_max_domain = projection_extrema(_domain_bounds(solver), direction)
|
|
76
|
+
return _make_auxiliary_grid(solver, s_min=s_min_domain, s_max=s_max_domain, ds=ds)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _set_tfsf_state(
|
|
80
|
+
solver,
|
|
81
|
+
*,
|
|
82
|
+
provider,
|
|
83
|
+
source,
|
|
84
|
+
lower,
|
|
85
|
+
upper,
|
|
86
|
+
electric_terms,
|
|
87
|
+
magnetic_terms,
|
|
88
|
+
auxiliary_grid=None,
|
|
89
|
+
phase_speed=None,
|
|
90
|
+
bounds=None,
|
|
91
|
+
):
|
|
92
|
+
injection = source["injection"]
|
|
93
|
+
state = {
|
|
94
|
+
"provider": provider,
|
|
95
|
+
"lower": lower,
|
|
96
|
+
"upper": upper,
|
|
97
|
+
"bounds": injection.get("bounds") if bounds is None else bounds,
|
|
98
|
+
"mode": injection.get("mode", "box"),
|
|
99
|
+
"electric_terms": electric_terms,
|
|
100
|
+
"magnetic_terms": magnetic_terms,
|
|
101
|
+
}
|
|
102
|
+
if injection.get("axis") is not None:
|
|
103
|
+
state["axis"] = injection["axis"]
|
|
104
|
+
if provider in {"plane_wave_ref_x_ez", "plane_wave_axis_aligned"}:
|
|
105
|
+
state["electric_batch"] = build_batched_reference_terms(solver, electric_terms)
|
|
106
|
+
state["magnetic_batch"] = build_batched_reference_terms(solver, magnetic_terms)
|
|
107
|
+
elif provider == "plane_wave_aux":
|
|
108
|
+
state["electric_batch"] = build_batched_aux_terms(solver, electric_terms)
|
|
109
|
+
state["magnetic_batch"] = build_batched_aux_terms(solver, magnetic_terms)
|
|
110
|
+
if auxiliary_grid is not None:
|
|
111
|
+
state["auxiliary_grid"] = auxiliary_grid
|
|
112
|
+
if phase_speed is not None:
|
|
113
|
+
state["phase_speed"] = phase_speed
|
|
114
|
+
solver._tfsf_state = state
|
|
115
|
+
solver.tfsf_enabled = True
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _validate_locally_uniform_region(solver, lower, upper):
|
|
119
|
+
"""Validate all three axes of a TFSF region and return the local spacings.
|
|
120
|
+
|
|
121
|
+
The specs touch the ``lower - 1`` / ``upper`` faces, so the primal-spacing
|
|
122
|
+
window is the region expanded by one cell on each side.
|
|
123
|
+
"""
|
|
124
|
+
return {
|
|
125
|
+
axis: require_locally_uniform_axis(
|
|
126
|
+
solver,
|
|
127
|
+
axis,
|
|
128
|
+
int(lo) - 1,
|
|
129
|
+
int(hi) + 1,
|
|
130
|
+
context="TFSF/PlaneWave injection",
|
|
131
|
+
)
|
|
132
|
+
for axis, lo, hi in zip("xyz", lower, upper)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def resolve_tfsf_region_indices(solver, injection):
|
|
137
|
+
mode = injection.get("mode", "box")
|
|
138
|
+
if mode == "box":
|
|
139
|
+
lower, upper = resolve_bounds_indices(solver.scene, injection["bounds"])
|
|
140
|
+
return lower, upper, injection["bounds"]
|
|
141
|
+
if mode != "slab":
|
|
142
|
+
raise ValueError(f"Unsupported TFSF injection mode: {mode!r}.")
|
|
143
|
+
|
|
144
|
+
axis = injection["axis"]
|
|
145
|
+
axis_index = AXIS_INDEX[axis]
|
|
146
|
+
shape = (solver.Nx, solver.Ny, solver.Nz)
|
|
147
|
+
lower = []
|
|
148
|
+
upper = []
|
|
149
|
+
bounds = []
|
|
150
|
+
for current_axis, axis_coords, size in zip("xyz", (solver.scene.x, solver.scene.y, solver.scene.z), shape):
|
|
151
|
+
if current_axis == axis:
|
|
152
|
+
axis_bounds = injection["axis_bounds"]
|
|
153
|
+
lower.append(nearest_index(axis_coords, axis_bounds[0]))
|
|
154
|
+
upper.append(nearest_index(axis_coords, axis_bounds[1]))
|
|
155
|
+
bounds.append((float(axis_bounds[0]), float(axis_bounds[1])))
|
|
156
|
+
else:
|
|
157
|
+
lo = int(solver.scene.pml_thickness_for_face(current_axis, "low"))
|
|
158
|
+
hi = int(size - solver.scene.pml_thickness_for_face(current_axis, "high") - 1)
|
|
159
|
+
lower.append(lo)
|
|
160
|
+
upper.append(hi)
|
|
161
|
+
bounds.append((float(axis_coords[lo].item()), float(axis_coords[hi].item())))
|
|
162
|
+
|
|
163
|
+
if upper[axis_index] <= lower[axis_index] + 1:
|
|
164
|
+
raise ValueError(f"TFSF slab bounds must span at least two cells along {axis}.")
|
|
165
|
+
for transverse_axis, lo, hi in zip("xyz", lower, upper):
|
|
166
|
+
if hi <= lo:
|
|
167
|
+
raise ValueError(f"TFSF slab transverse span is empty along {transverse_axis}.")
|
|
168
|
+
return tuple(lower), tuple(upper), tuple(bounds)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _validate_slab_interfaces_are_vacuum(solver, lower, upper, axis: str, tol=1e-5):
|
|
172
|
+
axis_index = AXIS_INDEX[axis]
|
|
173
|
+
for face_index in (lower[axis_index], upper[axis_index]):
|
|
174
|
+
face_slice = [
|
|
175
|
+
slice(lower[0], upper[0] + 1),
|
|
176
|
+
slice(lower[1], upper[1] + 1),
|
|
177
|
+
slice(lower[2], upper[2] + 1),
|
|
178
|
+
]
|
|
179
|
+
face_slice[axis_index] = slice(face_index, face_index + 1)
|
|
180
|
+
face_slice = tuple(face_slice)
|
|
181
|
+
eps_face = solver.epsilon_r[face_slice]
|
|
182
|
+
mu_face = solver.mu_r[face_slice]
|
|
183
|
+
if not torch.allclose(eps_face, torch.ones_like(eps_face), atol=tol, rtol=0.0):
|
|
184
|
+
raise ValueError("TFSF slab interfaces must remain in vacuum.")
|
|
185
|
+
if not torch.allclose(mu_face, torch.ones_like(mu_face), atol=tol, rtol=0.0):
|
|
186
|
+
raise ValueError("TFSF slab interfaces must remain in vacuum.")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _validate_slab_normal_bounds(solver, lower, upper, axis: str):
|
|
190
|
+
axis_index = AXIS_INDEX[axis]
|
|
191
|
+
size = (solver.Nx, solver.Ny, solver.Nz)[axis_index]
|
|
192
|
+
lo = lower[axis_index]
|
|
193
|
+
hi = upper[axis_index]
|
|
194
|
+
low_margin = solver.scene.pml_thickness_for_face(axis, "low")
|
|
195
|
+
high_margin = solver.scene.pml_thickness_for_face(axis, "high")
|
|
196
|
+
if hi <= lo + 1:
|
|
197
|
+
raise ValueError(f"TFSF slab bounds must span at least two cells along {axis}.")
|
|
198
|
+
if lo < low_margin or hi > size - high_margin - 1:
|
|
199
|
+
raise ValueError("TFSF slab bounds must lie strictly inside the non-PML simulation region.")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _initialize_grating_slab_cw_state(solver, source, lower, upper, bounds, deltas):
|
|
203
|
+
if source["kind"] != "plane_wave":
|
|
204
|
+
raise ValueError("Grating TFSF slab injection requires a PlaneWave source.")
|
|
205
|
+
axis = source["injection"]["axis"]
|
|
206
|
+
# Any of the three normal axes is supported: ``build_slab_tfsf_specs`` selects
|
|
207
|
+
# the two faces normal to ``axis``, ``discrete_plane_wave_vectors`` resolves the
|
|
208
|
+
# oblique polarization from the direction, and the mixed Bloch/CPML runtime and
|
|
209
|
+
# adjoint replay each dispatch on the single PML axis rather than assuming z.
|
|
210
|
+
# The transverse-axis topology (two Bloch axes, PML on the normal axis) is
|
|
211
|
+
# validated upstream by ``validate_grating_tfsf_slab_topology``.
|
|
212
|
+
#
|
|
213
|
+
# Both CW and pulsed (broadband) source times are supported: the fixed Bloch
|
|
214
|
+
# transverse phase is imposed by the boundary, while ``build_term_from_profile``
|
|
215
|
+
# emits CW-phased patches for a CW source_time and delayed-pulse patches
|
|
216
|
+
# otherwise. Automatic Bloch-wavevector resolution still requires CW (it maps
|
|
217
|
+
# a single frequency to a wavevector); that constraint is enforced upstream in
|
|
218
|
+
# ``resolve_bloch_wavevector`` before this state is built.
|
|
219
|
+
source_time = source["source_time"]
|
|
220
|
+
|
|
221
|
+
eta0 = (solver.mu0 / solver.eps0) ** 0.5
|
|
222
|
+
source_frequency = float(source_time["frequency"])
|
|
223
|
+
source_omega = 2.0 * np.pi * source_frequency
|
|
224
|
+
k_numeric = solve_numerical_wavenumber(solver, source["direction"], deltas)
|
|
225
|
+
phase_speed = solver.c if k_numeric <= 1e-12 else source_omega / k_numeric
|
|
226
|
+
electric_vector, magnetic_unit_vector_map = discrete_plane_wave_vectors(
|
|
227
|
+
source["direction"],
|
|
228
|
+
source["polarization"],
|
|
229
|
+
k_numeric,
|
|
230
|
+
deltas,
|
|
231
|
+
)
|
|
232
|
+
magnetic_vector = {name: value / eta0 for name, value in magnetic_unit_vector_map.items()}
|
|
233
|
+
reference_point = box_center_tensor(solver, bounds)
|
|
234
|
+
electric_specs, magnetic_specs = build_slab_tfsf_specs(lower, upper, axis=axis)
|
|
235
|
+
|
|
236
|
+
def build_discrete_cw_term(spec, coeff_patch, component_scale):
|
|
237
|
+
_, delay_patch = incident_profile(
|
|
238
|
+
solver,
|
|
239
|
+
{"kind": "plane_wave", "direction": source["direction"]},
|
|
240
|
+
_spec_positions(solver, spec),
|
|
241
|
+
reference_point,
|
|
242
|
+
phase_speed=phase_speed,
|
|
243
|
+
source_frequency=source_frequency,
|
|
244
|
+
)
|
|
245
|
+
return build_term_from_profile(
|
|
246
|
+
solver,
|
|
247
|
+
field_name=spec["field_name"],
|
|
248
|
+
offsets=spec["offsets"],
|
|
249
|
+
scale=spec["sign"] * component_scale * coeff_patch,
|
|
250
|
+
delay_patch=delay_patch,
|
|
251
|
+
activation_delay_patch=None,
|
|
252
|
+
source_time=source_time,
|
|
253
|
+
omega=source_omega,
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
electric_terms = build_terms_from_specs(
|
|
257
|
+
solver,
|
|
258
|
+
electric_specs,
|
|
259
|
+
magnetic_vector,
|
|
260
|
+
E_CURL_ATTR,
|
|
261
|
+
build_discrete_cw_term,
|
|
262
|
+
)
|
|
263
|
+
magnetic_terms = build_terms_from_specs(
|
|
264
|
+
solver,
|
|
265
|
+
magnetic_specs,
|
|
266
|
+
electric_vector,
|
|
267
|
+
H_CURL_ATTR,
|
|
268
|
+
build_discrete_cw_term,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
_set_tfsf_state(
|
|
272
|
+
solver,
|
|
273
|
+
provider="plane_wave_grating_slab_cw",
|
|
274
|
+
source=source,
|
|
275
|
+
lower=lower,
|
|
276
|
+
upper=upper,
|
|
277
|
+
bounds=bounds,
|
|
278
|
+
phase_speed=phase_speed,
|
|
279
|
+
electric_terms=electric_terms,
|
|
280
|
+
magnetic_terms=magnetic_terms,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def _spec_positions(solver, spec):
|
|
285
|
+
return solver._component_positions(
|
|
286
|
+
spec["incident_name"],
|
|
287
|
+
spec["sample_offsets"],
|
|
288
|
+
spec["sample_shape"],
|
|
289
|
+
dtype=solver.Ex.dtype,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _initialize_axis_aligned_plane_wave_auxiliary_state(
|
|
294
|
+
solver, source, lower, upper, axis: str, direction_sign: int, deltas, *, slab_axis=None
|
|
295
|
+
):
|
|
296
|
+
electric_vector = {
|
|
297
|
+
"Ex": source["polarization"][0],
|
|
298
|
+
"Ey": source["polarization"][1],
|
|
299
|
+
"Ez": source["polarization"][2],
|
|
300
|
+
}
|
|
301
|
+
magnetic_vector = magnetic_unit_vector(source["direction"], source["polarization"])
|
|
302
|
+
aux = _make_directional_auxiliary_grid(solver, source["direction"], float(deltas[axis]))
|
|
303
|
+
|
|
304
|
+
# A slab injection keeps only the two faces normal to the propagation axis; the
|
|
305
|
+
# total-field region fills the whole transverse span, so no transverse-face
|
|
306
|
+
# corrections exist (the transverse boundary absorbs/wraps the field directly).
|
|
307
|
+
if slab_axis is None:
|
|
308
|
+
electric_specs, magnetic_specs = build_discrete_tfsf_specs(lower, upper)
|
|
309
|
+
else:
|
|
310
|
+
electric_specs, magnetic_specs = build_slab_tfsf_specs(lower, upper, axis=slab_axis)
|
|
311
|
+
|
|
312
|
+
def build_axis_term(spec, coeff_patch, component_scale):
|
|
313
|
+
sample_indices = axis_aligned_sample_indices(
|
|
314
|
+
solver,
|
|
315
|
+
axis,
|
|
316
|
+
direction_sign,
|
|
317
|
+
spec["sample_kind"],
|
|
318
|
+
spec["sample_offsets"],
|
|
319
|
+
spec["sample_shape"],
|
|
320
|
+
)
|
|
321
|
+
term = make_reference_term(
|
|
322
|
+
solver,
|
|
323
|
+
field_name=spec["field_name"],
|
|
324
|
+
offsets=spec["offsets"],
|
|
325
|
+
coeff_patch=spec["sign"] * coeff_patch,
|
|
326
|
+
sample_kind=spec["sample_kind"],
|
|
327
|
+
sample_indices=sample_indices,
|
|
328
|
+
component_scale=component_scale,
|
|
329
|
+
)
|
|
330
|
+
term["sample_view"] = axis_aligned_sample_view(axis, sample_indices.numel())
|
|
331
|
+
return term
|
|
332
|
+
|
|
333
|
+
electric_terms = build_terms_from_specs(
|
|
334
|
+
solver,
|
|
335
|
+
electric_specs,
|
|
336
|
+
magnetic_vector,
|
|
337
|
+
E_CURL_ATTR,
|
|
338
|
+
build_axis_term,
|
|
339
|
+
)
|
|
340
|
+
magnetic_terms = build_terms_from_specs(
|
|
341
|
+
solver,
|
|
342
|
+
magnetic_specs,
|
|
343
|
+
electric_vector,
|
|
344
|
+
H_CURL_ATTR,
|
|
345
|
+
build_axis_term,
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
_set_tfsf_state(
|
|
349
|
+
solver,
|
|
350
|
+
provider="plane_wave_axis_aligned",
|
|
351
|
+
source=source,
|
|
352
|
+
lower=lower,
|
|
353
|
+
upper=upper,
|
|
354
|
+
auxiliary_grid=aux,
|
|
355
|
+
electric_terms=electric_terms,
|
|
356
|
+
magnetic_terms=magnetic_terms,
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _initialize_plane_wave_discrete_cw_state(solver, source, lower, upper, deltas):
|
|
361
|
+
eta0 = (solver.mu0 / solver.eps0) ** 0.5
|
|
362
|
+
source_time = source["source_time"]
|
|
363
|
+
source_frequency = float(source_time["frequency"])
|
|
364
|
+
source_omega = 2.0 * np.pi * source_frequency
|
|
365
|
+
k_numeric = solve_numerical_wavenumber(solver, source["direction"], deltas)
|
|
366
|
+
phase_speed = solver.c if k_numeric <= 1e-12 else source_omega / k_numeric
|
|
367
|
+
electric_vector, magnetic_unit_vector_map = discrete_plane_wave_vectors(
|
|
368
|
+
source["direction"],
|
|
369
|
+
source["polarization"],
|
|
370
|
+
k_numeric,
|
|
371
|
+
deltas,
|
|
372
|
+
)
|
|
373
|
+
magnetic_vector = {name: value / eta0 for name, value in magnetic_unit_vector_map.items()}
|
|
374
|
+
box_center = box_center_tensor(solver, source["injection"]["bounds"])
|
|
375
|
+
|
|
376
|
+
electric_specs, magnetic_specs = build_discrete_tfsf_specs(lower, upper)
|
|
377
|
+
|
|
378
|
+
def build_discrete_cw_term(spec, coeff_patch, component_scale):
|
|
379
|
+
_, delay_patch = incident_profile(
|
|
380
|
+
solver,
|
|
381
|
+
{"kind": "plane_wave", "direction": source["direction"]},
|
|
382
|
+
_spec_positions(solver, spec),
|
|
383
|
+
box_center,
|
|
384
|
+
phase_speed=phase_speed,
|
|
385
|
+
source_frequency=source_frequency,
|
|
386
|
+
)
|
|
387
|
+
return build_term_from_profile(
|
|
388
|
+
solver,
|
|
389
|
+
field_name=spec["field_name"],
|
|
390
|
+
offsets=spec["offsets"],
|
|
391
|
+
scale=spec["sign"] * component_scale * coeff_patch,
|
|
392
|
+
delay_patch=delay_patch,
|
|
393
|
+
activation_delay_patch=None,
|
|
394
|
+
source_time=source_time,
|
|
395
|
+
omega=source_omega,
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
electric_terms = build_terms_from_specs(
|
|
399
|
+
solver,
|
|
400
|
+
electric_specs,
|
|
401
|
+
magnetic_vector,
|
|
402
|
+
E_CURL_ATTR,
|
|
403
|
+
build_discrete_cw_term,
|
|
404
|
+
)
|
|
405
|
+
magnetic_terms = build_terms_from_specs(
|
|
406
|
+
solver,
|
|
407
|
+
magnetic_specs,
|
|
408
|
+
electric_vector,
|
|
409
|
+
H_CURL_ATTR,
|
|
410
|
+
build_discrete_cw_term,
|
|
411
|
+
)
|
|
412
|
+
|
|
413
|
+
_set_tfsf_state(
|
|
414
|
+
solver,
|
|
415
|
+
provider="plane_wave_discrete_cw",
|
|
416
|
+
source=source,
|
|
417
|
+
lower=lower,
|
|
418
|
+
upper=upper,
|
|
419
|
+
phase_speed=phase_speed,
|
|
420
|
+
electric_terms=electric_terms,
|
|
421
|
+
magnetic_terms=magnetic_terms,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _initialize_reference_plane_wave_auxiliary_state(solver, source, lower, upper, deltas):
|
|
426
|
+
ix0, iy0, iz0 = lower
|
|
427
|
+
ix1, iy1, iz1 = upper
|
|
428
|
+
|
|
429
|
+
dx_local = float(deltas["x"])
|
|
430
|
+
dy_local = float(deltas["y"])
|
|
431
|
+
dz_local = float(deltas["z"])
|
|
432
|
+
electric_scale = float(source["polarization"][2])
|
|
433
|
+
aux = _make_auxiliary_grid(
|
|
434
|
+
solver,
|
|
435
|
+
s_min=float(solver.scene.domain_range[0]),
|
|
436
|
+
s_max=float(solver.scene.domain_range[1]),
|
|
437
|
+
ds=dx_local,
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
magnetic_terms = [
|
|
441
|
+
make_reference_term(
|
|
442
|
+
solver,
|
|
443
|
+
field_name="Hy",
|
|
444
|
+
offsets=(ix0 - 1, iy0, iz0),
|
|
445
|
+
coeff_patch=solver.chy_curl[ix0 - 1 : ix0, iy0 : iy1 + 1, iz0:iz1] / dx_local,
|
|
446
|
+
sample_kind="electric",
|
|
447
|
+
sample_indices=constant_line_index_tensor(solver, ix0),
|
|
448
|
+
component_scale=+electric_scale,
|
|
449
|
+
),
|
|
450
|
+
make_reference_term(
|
|
451
|
+
solver,
|
|
452
|
+
field_name="Hy",
|
|
453
|
+
offsets=(ix1, iy0, iz0),
|
|
454
|
+
coeff_patch=-solver.chy_curl[ix1 : ix1 + 1, iy0 : iy1 + 1, iz0:iz1] / dx_local,
|
|
455
|
+
sample_kind="electric",
|
|
456
|
+
sample_indices=constant_line_index_tensor(solver, ix1),
|
|
457
|
+
component_scale=+electric_scale,
|
|
458
|
+
),
|
|
459
|
+
make_reference_term(
|
|
460
|
+
solver,
|
|
461
|
+
field_name="Hx",
|
|
462
|
+
offsets=(ix0, iy0 - 1, iz0),
|
|
463
|
+
coeff_patch=-solver.chx_curl[ix0 : ix1 + 1, iy0 - 1 : iy0, iz0:iz1] / dy_local,
|
|
464
|
+
sample_kind="electric",
|
|
465
|
+
sample_indices=line_index_tensor(solver, ix0, ix1 - ix0 + 1),
|
|
466
|
+
component_scale=+electric_scale,
|
|
467
|
+
),
|
|
468
|
+
make_reference_term(
|
|
469
|
+
solver,
|
|
470
|
+
field_name="Hx",
|
|
471
|
+
offsets=(ix0, iy1, iz0),
|
|
472
|
+
coeff_patch=solver.chx_curl[ix0 : ix1 + 1, iy1 : iy1 + 1, iz0:iz1] / dy_local,
|
|
473
|
+
sample_kind="electric",
|
|
474
|
+
sample_indices=line_index_tensor(solver, ix0, ix1 - ix0 + 1),
|
|
475
|
+
component_scale=+electric_scale,
|
|
476
|
+
),
|
|
477
|
+
]
|
|
478
|
+
|
|
479
|
+
electric_terms = [
|
|
480
|
+
make_reference_term(
|
|
481
|
+
solver,
|
|
482
|
+
field_name="Ez",
|
|
483
|
+
offsets=(ix0, iy0, iz0),
|
|
484
|
+
coeff_patch=-solver.cez_curl[ix0 : ix0 + 1, iy0 : iy1 + 1, iz0:iz1] / dx_local,
|
|
485
|
+
sample_kind="magnetic",
|
|
486
|
+
sample_indices=constant_line_index_tensor(solver, ix0 - 1),
|
|
487
|
+
component_scale=1.0,
|
|
488
|
+
),
|
|
489
|
+
make_reference_term(
|
|
490
|
+
solver,
|
|
491
|
+
field_name="Ez",
|
|
492
|
+
offsets=(ix1, iy0, iz0),
|
|
493
|
+
coeff_patch=solver.cez_curl[ix1 : ix1 + 1, iy0 : iy1 + 1, iz0:iz1] / dx_local,
|
|
494
|
+
sample_kind="magnetic",
|
|
495
|
+
sample_indices=constant_line_index_tensor(solver, ix1),
|
|
496
|
+
component_scale=1.0,
|
|
497
|
+
),
|
|
498
|
+
make_reference_term(
|
|
499
|
+
solver,
|
|
500
|
+
field_name="Ex",
|
|
501
|
+
offsets=(ix0, iy0, iz0),
|
|
502
|
+
coeff_patch=solver.cex_curl[ix0:ix1, iy0 : iy1 + 1, iz0 : iz0 + 1] / dz_local,
|
|
503
|
+
sample_kind="magnetic",
|
|
504
|
+
sample_indices=line_index_tensor(solver, ix0, ix1 - ix0),
|
|
505
|
+
component_scale=1.0,
|
|
506
|
+
),
|
|
507
|
+
make_reference_term(
|
|
508
|
+
solver,
|
|
509
|
+
field_name="Ex",
|
|
510
|
+
offsets=(ix0, iy0, iz1),
|
|
511
|
+
coeff_patch=-solver.cex_curl[ix0:ix1, iy0 : iy1 + 1, iz1 : iz1 + 1] / dz_local,
|
|
512
|
+
sample_kind="magnetic",
|
|
513
|
+
sample_indices=line_index_tensor(solver, ix0, ix1 - ix0),
|
|
514
|
+
component_scale=1.0,
|
|
515
|
+
),
|
|
516
|
+
]
|
|
517
|
+
|
|
518
|
+
_set_tfsf_state(
|
|
519
|
+
solver,
|
|
520
|
+
provider="plane_wave_ref_x_ez",
|
|
521
|
+
source=source,
|
|
522
|
+
lower=lower,
|
|
523
|
+
upper=upper,
|
|
524
|
+
auxiliary_grid=aux,
|
|
525
|
+
electric_terms=electric_terms,
|
|
526
|
+
magnetic_terms=magnetic_terms,
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _initialize_plane_wave_auxiliary_state(solver, source, lower, upper, deltas):
|
|
531
|
+
if is_reference_plane_wave_x_ez(source):
|
|
532
|
+
_initialize_reference_plane_wave_auxiliary_state(solver, source, lower, upper, deltas)
|
|
533
|
+
return
|
|
534
|
+
|
|
535
|
+
axis_direction = axis_aligned_direction(source["direction"])
|
|
536
|
+
if axis_direction is not None:
|
|
537
|
+
_initialize_axis_aligned_plane_wave_auxiliary_state(
|
|
538
|
+
solver,
|
|
539
|
+
source,
|
|
540
|
+
lower,
|
|
541
|
+
upper,
|
|
542
|
+
axis_direction[0],
|
|
543
|
+
axis_direction[1],
|
|
544
|
+
deltas,
|
|
545
|
+
)
|
|
546
|
+
return
|
|
547
|
+
|
|
548
|
+
if source["source_time"]["kind"] == "cw":
|
|
549
|
+
_initialize_plane_wave_discrete_cw_state(solver, source, lower, upper, deltas)
|
|
550
|
+
return
|
|
551
|
+
|
|
552
|
+
k_numeric = solve_numerical_wavenumber(solver, source["direction"], deltas)
|
|
553
|
+
source_omega = 2.0 * np.pi * float(source["source_time"]["frequency"])
|
|
554
|
+
phase_speed = solver.c if k_numeric <= 1e-12 else source_omega / k_numeric
|
|
555
|
+
electric_vector, magnetic_vector = discrete_plane_wave_vectors(
|
|
556
|
+
source["direction"],
|
|
557
|
+
source["polarization"],
|
|
558
|
+
k_numeric,
|
|
559
|
+
deltas,
|
|
560
|
+
)
|
|
561
|
+
ds = solve_auxiliary_step(solver, k_numeric)
|
|
562
|
+
if ds <= 0.0:
|
|
563
|
+
raise ValueError("Failed to construct a stable auxiliary TFSF line: ds must be > 0.")
|
|
564
|
+
|
|
565
|
+
aux = _make_directional_auxiliary_grid(solver, source["direction"], ds)
|
|
566
|
+
electric_specs, magnetic_specs = build_discrete_tfsf_specs(lower, upper)
|
|
567
|
+
|
|
568
|
+
def build_aux_term(spec, coeff_patch, component_scale):
|
|
569
|
+
sample_positions = project_positions(_spec_positions(solver, spec), source["direction"])
|
|
570
|
+
return make_aux_term(
|
|
571
|
+
solver,
|
|
572
|
+
field_name=spec["field_name"],
|
|
573
|
+
offsets=spec["offsets"],
|
|
574
|
+
coeff_patch=spec["sign"] * coeff_patch,
|
|
575
|
+
sample_positions=sample_positions,
|
|
576
|
+
component_scale=component_scale,
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
electric_terms = build_terms_from_specs(
|
|
580
|
+
solver,
|
|
581
|
+
electric_specs,
|
|
582
|
+
magnetic_vector,
|
|
583
|
+
E_CURL_ATTR,
|
|
584
|
+
build_aux_term,
|
|
585
|
+
)
|
|
586
|
+
magnetic_terms = build_terms_from_specs(
|
|
587
|
+
solver,
|
|
588
|
+
magnetic_specs,
|
|
589
|
+
electric_vector,
|
|
590
|
+
H_CURL_ATTR,
|
|
591
|
+
build_aux_term,
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
_set_tfsf_state(
|
|
595
|
+
solver,
|
|
596
|
+
provider="plane_wave_aux",
|
|
597
|
+
source=source,
|
|
598
|
+
lower=lower,
|
|
599
|
+
upper=upper,
|
|
600
|
+
auxiliary_grid=aux,
|
|
601
|
+
phase_speed=phase_speed,
|
|
602
|
+
electric_terms=electric_terms,
|
|
603
|
+
magnetic_terms=magnetic_terms,
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def _initialize_analytic_tfsf_state(solver, source, lower, upper, deltas):
|
|
608
|
+
box_center = box_center_tensor(solver, source["injection"]["bounds"])
|
|
609
|
+
eta0 = (solver.mu0 / solver.eps0) ** 0.5
|
|
610
|
+
source_time = source["source_time"]
|
|
611
|
+
source_frequency = float(source_time["frequency"])
|
|
612
|
+
source_omega = 2.0 * np.pi * source_frequency
|
|
613
|
+
k_numeric = solve_numerical_wavenumber(solver, source["direction"], deltas)
|
|
614
|
+
phase_speed = solver.c if k_numeric <= 1e-12 else source_omega / k_numeric
|
|
615
|
+
entry_s = entry_projection(source["injection"]["bounds"], source["direction"])
|
|
616
|
+
|
|
617
|
+
if source["kind"] in {"gaussian_beam", "astigmatic_gaussian_beam"}:
|
|
618
|
+
electric_vector, magnetic_unit_vector_map = discrete_plane_wave_vectors(
|
|
619
|
+
source["direction"],
|
|
620
|
+
source["polarization"],
|
|
621
|
+
k_numeric,
|
|
622
|
+
deltas,
|
|
623
|
+
)
|
|
624
|
+
profile_polarization = (
|
|
625
|
+
electric_vector["Ex"],
|
|
626
|
+
electric_vector["Ey"],
|
|
627
|
+
electric_vector["Ez"],
|
|
628
|
+
)
|
|
629
|
+
magnetic_vector = {name: value / eta0 for name, value in magnetic_unit_vector_map.items()}
|
|
630
|
+
else:
|
|
631
|
+
electric_vector = {
|
|
632
|
+
"Ex": source["polarization"][0],
|
|
633
|
+
"Ey": source["polarization"][1],
|
|
634
|
+
"Ez": source["polarization"][2],
|
|
635
|
+
}
|
|
636
|
+
profile_polarization = None
|
|
637
|
+
magnetic_vector = magnetic_physical_vector(source["direction"], source["polarization"], eta0)
|
|
638
|
+
|
|
639
|
+
electric_specs, magnetic_specs = build_discrete_tfsf_specs(lower, upper)
|
|
640
|
+
|
|
641
|
+
def build_profile_term(spec, coeff_patch, component_scale):
|
|
642
|
+
positions = _spec_positions(solver, spec)
|
|
643
|
+
spatial_amplitude, delay_patch = incident_profile(
|
|
644
|
+
solver,
|
|
645
|
+
source,
|
|
646
|
+
positions,
|
|
647
|
+
box_center,
|
|
648
|
+
phase_speed=phase_speed,
|
|
649
|
+
source_frequency=source_frequency,
|
|
650
|
+
polarization_override=profile_polarization,
|
|
651
|
+
)
|
|
652
|
+
activation_delay_patch = torch.clamp(
|
|
653
|
+
project_positions(positions, source["direction"]) - entry_s,
|
|
654
|
+
min=0.0,
|
|
655
|
+
) / float(phase_speed)
|
|
656
|
+
if source["kind"] == "plane_wave":
|
|
657
|
+
delay_patch = activation_delay_patch
|
|
658
|
+
scale = spec["sign"] * component_scale * spatial_amplitude * coeff_patch
|
|
659
|
+
return build_term_from_profile(
|
|
660
|
+
solver,
|
|
661
|
+
field_name=spec["field_name"],
|
|
662
|
+
offsets=spec["offsets"],
|
|
663
|
+
scale=scale,
|
|
664
|
+
delay_patch=delay_patch,
|
|
665
|
+
activation_delay_patch=activation_delay_patch,
|
|
666
|
+
source_time=source_time,
|
|
667
|
+
omega=source_omega,
|
|
668
|
+
)
|
|
669
|
+
|
|
670
|
+
electric_terms = build_terms_from_specs(
|
|
671
|
+
solver,
|
|
672
|
+
electric_specs,
|
|
673
|
+
magnetic_vector,
|
|
674
|
+
E_CURL_ATTR,
|
|
675
|
+
build_profile_term,
|
|
676
|
+
)
|
|
677
|
+
magnetic_terms = build_terms_from_specs(
|
|
678
|
+
solver,
|
|
679
|
+
magnetic_specs,
|
|
680
|
+
electric_vector,
|
|
681
|
+
H_CURL_ATTR,
|
|
682
|
+
build_profile_term,
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
_set_tfsf_state(
|
|
686
|
+
solver,
|
|
687
|
+
provider="analytic_profile",
|
|
688
|
+
source=source,
|
|
689
|
+
lower=lower,
|
|
690
|
+
upper=upper,
|
|
691
|
+
phase_speed=phase_speed,
|
|
692
|
+
electric_terms=electric_terms,
|
|
693
|
+
magnetic_terms=magnetic_terms,
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _initialize_nonperiodic_slab_state(solver, source, lower, upper, deltas):
|
|
698
|
+
"""Slab TFSF with non-periodic (PML/absorbing) transverse boundaries.
|
|
699
|
+
|
|
700
|
+
A two-face slab injects a plane wave that fills the whole transverse cross
|
|
701
|
+
section. With absorbing transverse boundaries this is only well posed for a
|
|
702
|
+
wave that is uniform across that cross section, i.e. normal incidence along
|
|
703
|
+
the slab axis; an oblique wave would carry a transverse phase gradient that
|
|
704
|
+
the omitted transverse faces cannot terminate (that case needs Bloch
|
|
705
|
+
transverse boundaries and is handled by the grating slab path). The state is
|
|
706
|
+
built on the same axis-aligned auxiliary-line provider the box TFSF uses, so
|
|
707
|
+
both CW and pulsed source_times are supported and the forward/adjoint runtime
|
|
708
|
+
needs no slab-specific branch.
|
|
709
|
+
"""
|
|
710
|
+
axis = source["injection"]["axis"]
|
|
711
|
+
if source["kind"] != "plane_wave":
|
|
712
|
+
raise ValueError("Non-periodic TFSF slab injection requires a PlaneWave source.")
|
|
713
|
+
axis_direction = axis_aligned_direction(source["direction"])
|
|
714
|
+
if axis_direction is None or axis_direction[0] != axis:
|
|
715
|
+
raise ValueError(
|
|
716
|
+
"A TFSF slab with non-periodic transverse boundaries must be a plane wave "
|
|
717
|
+
f"normally incident along the slab axis {axis!r}; use Bloch transverse "
|
|
718
|
+
"boundaries for oblique-incidence (grating) slab injection."
|
|
719
|
+
)
|
|
720
|
+
_initialize_axis_aligned_plane_wave_auxiliary_state(
|
|
721
|
+
solver,
|
|
722
|
+
source,
|
|
723
|
+
lower,
|
|
724
|
+
upper,
|
|
725
|
+
axis_direction[0],
|
|
726
|
+
axis_direction[1],
|
|
727
|
+
deltas,
|
|
728
|
+
slab_axis=axis,
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
def initialize_tfsf_state(solver):
|
|
733
|
+
compiled_sources = tuple(getattr(solver, "_compiled_sources", ()) or ())
|
|
734
|
+
if not compiled_sources and getattr(solver, "_compiled_source", None) is not None:
|
|
735
|
+
compiled_sources = (solver._compiled_source,)
|
|
736
|
+
tfsf_sources = [source for source in compiled_sources if source.get("injection", {}).get("kind") == "tfsf"]
|
|
737
|
+
if not tfsf_sources:
|
|
738
|
+
solver.tfsf_enabled = False
|
|
739
|
+
solver._tfsf_state = None
|
|
740
|
+
return
|
|
741
|
+
|
|
742
|
+
if len(tfsf_sources) > 1:
|
|
743
|
+
raise ValueError("FDTD currently supports at most one TFSF source per scene.")
|
|
744
|
+
|
|
745
|
+
source = tfsf_sources[0]
|
|
746
|
+
if source["kind"] not in {"plane_wave", "gaussian_beam", "astigmatic_gaussian_beam"}:
|
|
747
|
+
raise ValueError(
|
|
748
|
+
"TFSF injection currently supports PlaneWave, GaussianBeam, and AstigmaticGaussianBeam only."
|
|
749
|
+
)
|
|
750
|
+
if source["injection"].get("mode", "box") == "slab":
|
|
751
|
+
if solver.scene.boundary.uses_kind("bloch"):
|
|
752
|
+
validate_grating_tfsf_slab_topology(solver)
|
|
753
|
+
lower, upper, bounds = resolve_tfsf_region_indices(solver, source["injection"])
|
|
754
|
+
deltas = _validate_locally_uniform_region(solver, lower, upper)
|
|
755
|
+
_validate_slab_normal_bounds(solver, lower, upper, source["injection"]["axis"])
|
|
756
|
+
_validate_slab_interfaces_are_vacuum(solver, lower, upper, source["injection"]["axis"])
|
|
757
|
+
_initialize_grating_slab_cw_state(solver, source, lower, upper, bounds, deltas)
|
|
758
|
+
return
|
|
759
|
+
axis = source["injection"]["axis"]
|
|
760
|
+
lower, upper, _bounds = resolve_tfsf_region_indices(solver, source["injection"])
|
|
761
|
+
deltas = _validate_locally_uniform_region(solver, lower, upper)
|
|
762
|
+
_validate_slab_normal_bounds(solver, lower, upper, axis)
|
|
763
|
+
_validate_slab_interfaces_are_vacuum(solver, lower, upper, axis)
|
|
764
|
+
_initialize_nonperiodic_slab_state(solver, source, lower, upper, deltas)
|
|
765
|
+
return
|
|
766
|
+
if solver.scene.boundary.uses_kind("bloch"):
|
|
767
|
+
raise NotImplementedError("TFSF slab mode is required for Bloch-boundary TFSF injection.")
|
|
768
|
+
if solver.scene.boundary.uses_kind("periodic"):
|
|
769
|
+
raise NotImplementedError("TFSF injection currently supports only none, pml, pec, or pmc boundaries.")
|
|
770
|
+
|
|
771
|
+
lower, upper, _ = resolve_tfsf_region_indices(solver, source["injection"])
|
|
772
|
+
deltas = _validate_locally_uniform_region(solver, lower, upper)
|
|
773
|
+
validate_bounds(solver, lower, upper)
|
|
774
|
+
validate_background_is_vacuum(solver, lower, upper)
|
|
775
|
+
if source["kind"] == "plane_wave":
|
|
776
|
+
_initialize_plane_wave_auxiliary_state(solver, source, lower, upper, deltas)
|
|
777
|
+
return
|
|
778
|
+
_initialize_analytic_tfsf_state(solver, source, lower, upper, deltas)
|